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.configuration.CmsADEConfigData;
031import org.opencms.ade.configuration.CmsADEManager;
032import org.opencms.ade.configuration.CmsFunctionReference;
033import org.opencms.ade.configuration.CmsResourceTypeConfig;
034import org.opencms.ade.configuration.plugins.CmsTemplatePlugin;
035import org.opencms.ade.configuration.plugins.CmsTemplatePluginFinder;
036import org.opencms.ade.containerpage.CmsContainerpageService;
037import org.opencms.ade.containerpage.CmsDetailOnlyContainerUtil;
038import org.opencms.ade.containerpage.CmsModelGroupHelper;
039import org.opencms.ade.containerpage.shared.CmsFormatterConfig;
040import org.opencms.ade.containerpage.shared.CmsInheritanceInfo;
041import org.opencms.ade.detailpage.CmsDetailPageInfo;
042import org.opencms.ade.detailpage.CmsDetailPageResourceHandler;
043import org.opencms.file.CmsFile;
044import org.opencms.file.CmsObject;
045import org.opencms.file.CmsProperty;
046import org.opencms.file.CmsPropertyDefinition;
047import org.opencms.file.CmsRequestContext;
048import org.opencms.file.CmsResource;
049import org.opencms.file.CmsResourceFilter;
050import org.opencms.file.CmsVfsResourceNotFoundException;
051import org.opencms.file.history.CmsHistoryResourceHandler;
052import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
053import org.opencms.file.types.I_CmsResourceType;
054import org.opencms.flex.CmsFlexController;
055import org.opencms.flex.CmsFlexRequest;
056import org.opencms.gwt.shared.CmsGwtConstants;
057import org.opencms.i18n.CmsEncoder;
058import org.opencms.i18n.CmsLocaleGroupService;
059import org.opencms.i18n.CmsMessageToBundleIndex;
060import org.opencms.i18n.CmsResourceBundleLoader;
061import org.opencms.i18n.CmsVfsResourceBundle;
062import org.opencms.jsp.CmsJspBean;
063import org.opencms.jsp.CmsJspResourceWrapper;
064import org.opencms.jsp.CmsJspTagContainer;
065import org.opencms.jsp.CmsJspTagEditable;
066import org.opencms.jsp.Messages;
067import org.opencms.jsp.jsonpart.CmsJsonPartFilter;
068import org.opencms.jsp.search.config.parser.simplesearch.CmsConfigParserUtils;
069import org.opencms.loader.CmsLoaderException;
070import org.opencms.loader.CmsTemplateContextManager;
071import org.opencms.main.CmsException;
072import org.opencms.main.CmsLog;
073import org.opencms.main.CmsRuntimeException;
074import org.opencms.main.CmsSystemInfo;
075import org.opencms.main.OpenCms;
076import org.opencms.main.OpenCmsServlet;
077import org.opencms.relations.CmsCategory;
078import org.opencms.relations.CmsCategoryService;
079import org.opencms.search.galleries.CmsGalleryNameMacroResolver;
080import org.opencms.site.CmsSite;
081import org.opencms.site.CmsSiteMatcher;
082import org.opencms.staticexport.CmsLinkManager;
083import org.opencms.ui.CmsVaadinUtils;
084import org.opencms.ui.apps.A_CmsWorkplaceApp;
085import org.opencms.ui.apps.CmsEditor;
086import org.opencms.ui.apps.CmsEditorConfiguration;
087import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditor;
088import org.opencms.util.CmsCollectionsGenericWrapper;
089import org.opencms.util.CmsFileUtil;
090import org.opencms.util.CmsMacroResolver;
091import org.opencms.util.CmsStringUtil;
092import org.opencms.util.CmsUUID;
093import org.opencms.workplace.galleries.CmsAjaxDownloadGallery;
094import org.opencms.workplace.galleries.CmsAjaxImageGallery;
095import org.opencms.xml.CmsXmlContentDefinition;
096import org.opencms.xml.containerpage.CmsADESessionCache;
097import org.opencms.xml.containerpage.CmsContainerBean;
098import org.opencms.xml.containerpage.CmsContainerElementBean;
099import org.opencms.xml.containerpage.CmsContainerPageBean;
100import org.opencms.xml.containerpage.CmsDynamicFunctionBean;
101import org.opencms.xml.containerpage.CmsDynamicFunctionParser;
102import org.opencms.xml.containerpage.CmsFormatterConfiguration;
103import org.opencms.xml.containerpage.CmsMetaMapping;
104import org.opencms.xml.containerpage.CmsXmlContainerPage;
105import org.opencms.xml.containerpage.CmsXmlContainerPageFactory;
106import org.opencms.xml.containerpage.I_CmsFormatterBean;
107import org.opencms.xml.content.CmsXmlContent;
108import org.opencms.xml.content.CmsXmlContentFactory;
109import org.opencms.xml.content.CmsXmlContentProperty;
110import org.opencms.xml.templatemapper.CmsTemplateMapper;
111import org.opencms.xml.types.I_CmsXmlContentValue;
112
113import java.lang.reflect.Constructor;
114import java.lang.reflect.Method;
115import java.net.URI;
116import java.net.URISyntaxException;
117import java.util.ArrayList;
118import java.util.Arrays;
119import java.util.Collection;
120import java.util.Collections;
121import java.util.Comparator;
122import java.util.HashMap;
123import java.util.HashSet;
124import java.util.List;
125import java.util.Locale;
126import java.util.Map;
127import java.util.ResourceBundle;
128import java.util.Set;
129import java.util.function.Predicate;
130import java.util.stream.Collectors;
131
132import javax.servlet.ServletRequest;
133import javax.servlet.http.HttpServletRequest;
134
135import org.apache.commons.collections.Transformer;
136import org.apache.commons.lang3.LocaleUtils;
137import org.apache.commons.logging.Log;
138
139import com.google.common.collect.ComparisonChain;
140import com.google.common.collect.Multimap;
141
142/**
143 * Allows convenient access to the most important OpenCms functions on a JSP page,
144 * indented to be used from a JSP with the JSTL or EL.<p>
145 *
146 * This bean is available by default in the context of an OpenCms managed JSP.<p>
147 *
148 * @since 8.0
149 */
150public final class CmsJspStandardContextBean {
151
152    /**
153     * Container element wrapper to add some API methods.<p>
154     */
155    public class CmsContainerElementWrapper extends CmsContainerElementBean {
156
157        /** Cache for the wrapped element parent. */
158        private CmsContainerElementWrapper m_parent;
159
160        /** Cache for the wrapped element type name. */
161        private String m_resourceTypeName;
162
163        /** The wrapped element instance. */
164        private CmsContainerElementBean m_wrappedElement;
165
166        /** Cache for the wrapped element settings. */
167        private Map<String, CmsJspElementSettingValueWrapper> m_wrappedSettings;
168
169        /** Cached formatter key - use array to distinguish between uncached and cached, but null. */
170        private String[] m_formatterKey;
171
172        /**
173         * Constructor.<p>
174         *
175         * @param element the element to wrap
176         */
177        protected CmsContainerElementWrapper(CmsContainerElementBean element) {
178
179            m_wrappedElement = element;
180
181        }
182
183        /**
184         * @see org.opencms.xml.containerpage.CmsContainerElementBean#clone()
185         */
186        @Override
187        public CmsContainerElementBean clone() {
188
189            return m_wrappedElement.clone();
190        }
191
192        /**
193         * @see org.opencms.xml.containerpage.CmsContainerElementBean#editorHash()
194         */
195        @Override
196        public String editorHash() {
197
198            return m_wrappedElement.editorHash();
199        }
200
201        /**
202         * @see org.opencms.xml.containerpage.CmsContainerElementBean#equals(java.lang.Object)
203         */
204        @Override
205        public boolean equals(Object obj) {
206
207            return m_wrappedElement.equals(obj);
208        }
209
210        /**
211         * @see org.opencms.xml.containerpage.CmsContainerElementBean#getFormatterId()
212         */
213        @Override
214        public CmsUUID getFormatterId() {
215
216            return m_wrappedElement.getFormatterId();
217        }
218
219        /**
220         * Returns the formatter key, if possible, otherwise the formatter configuration id, or null if nothing at all can be found.
221         *
222         * @return the formatter key
223         */
224        public String getFormatterKey() {
225
226            if (m_formatterKey == null) {
227                String key = null;
228                I_CmsFormatterBean formatter = getElementFormatter(m_wrappedElement);
229                if (formatter != null) {
230                    key = formatter.getKeyOrId();
231                }
232                m_formatterKey = new String[] {key};
233            }
234            return m_formatterKey[0];
235        }
236
237        /**
238         * @see org.opencms.xml.containerpage.CmsContainerElementBean#getId()
239         */
240        @Override
241        public CmsUUID getId() {
242
243            return m_wrappedElement.getId();
244        }
245
246        /**
247         * @see org.opencms.xml.containerpage.CmsContainerElementBean#getIndividualSettings()
248         */
249        @Override
250        public Map<String, String> getIndividualSettings() {
251
252            return m_wrappedElement.getIndividualSettings();
253        }
254
255        /**
256         * @see org.opencms.xml.containerpage.CmsContainerElementBean#getInheritanceInfo()
257         */
258        @Override
259        public CmsInheritanceInfo getInheritanceInfo() {
260
261            return m_wrappedElement.getInheritanceInfo();
262        }
263
264        /**
265         * @see org.opencms.xml.containerpage.CmsContainerElementBean#getInstanceId()
266         */
267        @Override
268        public String getInstanceId() {
269
270            return m_wrappedElement.getInstanceId();
271        }
272
273        /**
274         * Returns the parent element if present.<p>
275         *
276         * @return the parent element or <code>null</code> if not available
277         */
278        public CmsContainerElementWrapper getParent() {
279
280            if (m_parent == null) {
281                CmsContainerElementBean parent = getParentElement(m_wrappedElement);
282                m_parent = (parent != null) ? new CmsContainerElementWrapper(getParentElement(m_wrappedElement)) : null;
283            }
284            return m_parent;
285        }
286
287        /**
288         * @see org.opencms.xml.containerpage.CmsContainerElementBean#getResource()
289         */
290        @Override
291        public CmsResource getResource() {
292
293            return m_wrappedElement.getResource();
294        }
295
296        /**
297         * Returns the resource type name of the element resource.<p>
298         *
299         * @return the resource type name
300         */
301        public String getResourceTypeName() {
302
303            if (m_resourceTypeName == null) {
304                m_resourceTypeName = "";
305                try {
306                    m_resourceTypeName = OpenCms.getResourceManager().getResourceType(
307                        m_wrappedElement.getResource()).getTypeName();
308                } catch (Exception e) {
309                    CmsJspStandardContextBean.LOG.error(e.getLocalizedMessage(), e);
310                }
311            }
312            return m_resourceTypeName;
313        }
314
315        /**
316         * Returns a lazy initialized setting map.<p>
317         *
318         * The values returned in the map are instances of {@link A_CmsJspValueWrapper}.
319         *
320         * @return the wrapped settings
321         */
322        public Map<String, CmsJspElementSettingValueWrapper> getSetting() {
323
324            if (m_wrappedSettings == null) {
325                m_wrappedSettings = CmsCollectionsGenericWrapper.createLazyMap(
326                    new SettingsTransformer(m_wrappedElement));
327            }
328            return m_wrappedSettings;
329        }
330
331        /**
332         * @see org.opencms.xml.containerpage.CmsContainerElementBean#getSettings()
333         */
334        @Override
335        public Map<String, String> getSettings() {
336
337            return m_wrappedElement.getSettings();
338        }
339
340        /**
341         * @see org.opencms.xml.containerpage.CmsContainerElementBean#getSitePath()
342         */
343        @Override
344        public String getSitePath() {
345
346            return m_wrappedElement.getSitePath();
347        }
348
349        /**
350         * @see org.opencms.xml.containerpage.CmsContainerElementBean#hashCode()
351         */
352        @Override
353        public int hashCode() {
354
355            return m_wrappedElement.hashCode();
356        }
357
358        /**
359         * @see org.opencms.xml.containerpage.CmsContainerElementBean#initResource(org.opencms.file.CmsObject)
360         */
361        @Override
362        public void initResource(CmsObject cms) throws CmsException {
363
364            m_wrappedElement.initResource(cms);
365        }
366
367        /**
368         * @see org.opencms.xml.containerpage.CmsContainerElementBean#initSettings(org.opencms.file.CmsObject, org.opencms.ade.configuration.CmsADEConfigData, org.opencms.xml.containerpage.I_CmsFormatterBean, java.util.Locale, javax.servlet.ServletRequest, java.util.Map)
369         */
370        @Override
371        public void initSettings(
372            CmsObject cms,
373            CmsADEConfigData config,
374            I_CmsFormatterBean formatterBean,
375            Locale locale,
376            ServletRequest request,
377            Map<String, String> settingPresets) {
378
379            m_wrappedElement.initSettings(cms, config, formatterBean, locale, request, settingPresets);
380        }
381
382        /**
383         * @see org.opencms.xml.containerpage.CmsContainerElementBean#isCreateNew()
384         */
385        @Override
386        public boolean isCreateNew() {
387
388            return m_wrappedElement.isCreateNew();
389        }
390
391        /**
392         * @see org.opencms.xml.containerpage.CmsContainerElementBean#isGroupContainer(org.opencms.file.CmsObject)
393         */
394        @Override
395        public boolean isGroupContainer(CmsObject cms) throws CmsException {
396
397            return m_wrappedElement.isGroupContainer(cms);
398        }
399
400        /**
401         * @see org.opencms.xml.containerpage.CmsContainerElementBean#isHistoryContent()
402         */
403        @Override
404        public boolean isHistoryContent() {
405
406            return m_wrappedElement.isHistoryContent();
407        }
408
409        /**
410         * @see org.opencms.xml.containerpage.CmsContainerElementBean#isInheritedContainer(org.opencms.file.CmsObject)
411         */
412        @Override
413        public boolean isInheritedContainer(CmsObject cms) throws CmsException {
414
415            return m_wrappedElement.isInheritedContainer(cms);
416        }
417
418        /**
419         * @see org.opencms.xml.containerpage.CmsContainerElementBean#isInMemoryOnly()
420         */
421        @Override
422        public boolean isInMemoryOnly() {
423
424            return m_wrappedElement.isInMemoryOnly();
425        }
426
427        /**
428         * @see org.opencms.xml.containerpage.CmsContainerElementBean#isReleasedAndNotExpired()
429         */
430        @Override
431        public boolean isReleasedAndNotExpired() {
432
433            return m_wrappedElement.isReleasedAndNotExpired();
434        }
435
436        /**
437         * @see org.opencms.xml.containerpage.CmsContainerElementBean#isTemporaryContent()
438         */
439        @Override
440        public boolean isTemporaryContent() {
441
442            return m_wrappedElement.isTemporaryContent();
443        }
444
445        /**
446         * @see org.opencms.xml.containerpage.CmsContainerElementBean#setFormatterId(org.opencms.util.CmsUUID)
447         */
448        @Override
449        public void setFormatterId(CmsUUID formatterId) {
450
451            m_wrappedElement.setFormatterId(formatterId);
452        }
453
454        /**
455         * @see org.opencms.xml.containerpage.CmsContainerElementBean#setHistoryFile(org.opencms.file.CmsFile)
456         */
457        @Override
458        public void setHistoryFile(CmsFile file) {
459
460            m_wrappedElement.setHistoryFile(file);
461        }
462
463        /**
464         * @see org.opencms.xml.containerpage.CmsContainerElementBean#setInheritanceInfo(org.opencms.ade.containerpage.shared.CmsInheritanceInfo)
465         */
466        @Override
467        public void setInheritanceInfo(CmsInheritanceInfo inheritanceInfo) {
468
469            m_wrappedElement.setInheritanceInfo(inheritanceInfo);
470        }
471
472        /**
473         * @see org.opencms.xml.containerpage.CmsContainerElementBean#setTemporaryFile(org.opencms.file.CmsFile)
474         */
475        @Override
476        public void setTemporaryFile(CmsFile elementFile) {
477
478            m_wrappedElement.setTemporaryFile(elementFile);
479        }
480
481        /**
482         * @see org.opencms.xml.containerpage.CmsContainerElementBean#toString()
483         */
484        @Override
485        public String toString() {
486
487            return m_wrappedElement.toString();
488        }
489    }
490
491    /**
492     * Provides a lazy initialized Map that provides the detail page link as a value when given the name of a
493     * (named) dynamic function or resource type as a key.<p>
494     */
495    public class CmsDetailLookupTransformer implements Transformer {
496
497        /** The selected prefix. */
498        private String m_prefix;
499
500        /**
501         * Constructor with a prefix.<p>
502         *
503         * The prefix is used to distinguish between type detail pages and function detail pages.<p>
504         *
505         * @param prefix the prefix to use
506         */
507        public CmsDetailLookupTransformer(String prefix) {
508
509            m_prefix = prefix;
510        }
511
512        /**
513         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
514         */
515        @Override
516        public Object transform(Object input) {
517
518            String prefix = m_prefix;
519            CmsObject cms = m_cms;
520            String inputStr = String.valueOf(input);
521
522            return getFunctionDetailLink(cms, prefix, inputStr, false);
523        }
524
525    }
526
527    /**
528     * The element setting transformer.<p>
529     */
530    public class SettingsTransformer implements Transformer {
531
532        /** The element formatter config. */
533        private I_CmsFormatterBean m_formatter;
534
535        /** The configured formatter settings. */
536        private Map<String, CmsXmlContentProperty> m_formatterSettingsConfig;
537
538        /** The element. */
539        private CmsContainerElementBean m_transformElement;
540
541        /**
542         * Constructor.<p>
543         *
544         * @param element the element
545         */
546        SettingsTransformer(CmsContainerElementBean element) {
547
548            m_transformElement = element;
549            m_formatter = getElementFormatter(element);
550        }
551
552        /**
553         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
554         */
555        @Override
556        public Object transform(Object settingName) {
557
558            boolean exists;
559            if (m_formatter != null) {
560                if (m_formatterSettingsConfig == null) {
561                    m_formatterSettingsConfig = OpenCms.getADEManager().getFormatterSettings(
562                        m_cms,
563                        m_config,
564                        m_formatter,
565                        m_transformElement.getResource(),
566                        getLocale(),
567                        m_request);
568                }
569
570                // the first condition is used to catch shared settings of nested formatters,
571                // the second condition is used to catch settings with visibility parentShared
572                // (just because you can't edit them on the child element doesn't mean they don't exist!)
573                exists = (m_formatterSettingsConfig.get(settingName) != null)
574                    || m_formatter.getSettings(m_config).containsKey(settingName);
575            } else {
576                exists = m_transformElement.getSettings().get(settingName) != null;
577            }
578            return new CmsJspElementSettingValueWrapper(
579                CmsJspStandardContextBean.this,
580                m_transformElement.getSettings().get(settingName),
581                exists);
582        }
583    }
584
585    /**
586     * Bean containing a template name and URI.<p>
587     */
588    public static class TemplateBean {
589
590        /** True if the template context was manually selected. */
591        private boolean m_forced;
592
593        /** The template name. */
594        private String m_name;
595
596        /** The template resource. */
597        private CmsResource m_resource;
598
599        /** The template uri, if no resource is set. */
600        private String m_uri;
601
602        /**
603         * Creates a new instance.<p>
604         *
605         * @param name the template name
606         * @param resource the template resource
607         */
608        public TemplateBean(String name, CmsResource resource) {
609
610            m_resource = resource;
611            m_name = name;
612        }
613
614        /**
615         * Creates a new instance with an URI instead of a resoure.<p>
616         *
617         * @param name the template name
618         * @param uri the template uri
619         */
620        public TemplateBean(String name, String uri) {
621
622            m_name = name;
623            m_uri = uri;
624        }
625
626        /**
627         * Gets the template name.<p>
628         *
629         * @return the template name
630         */
631        public String getName() {
632
633            return m_name;
634        }
635
636        /**
637         * Gets the template resource.<p>
638         *
639         * @return the template resource
640         */
641        public CmsResource getResource() {
642
643            return m_resource;
644        }
645
646        /**
647         * Gets the template uri.<p>
648         *
649         * @return the template URI.
650         */
651        public String getUri() {
652
653            if (m_resource != null) {
654                return m_resource.getRootPath();
655            } else {
656                return m_uri;
657            }
658        }
659
660        /**
661         * Returns true if the template context was manually selected.<p>
662         *
663         * @return true if the template context was manually selected
664         */
665        public boolean isForced() {
666
667            return m_forced;
668        }
669
670        /**
671         * Sets the 'forced' flag to a new value.<p>
672         *
673         * @param forced the new value
674         */
675        public void setForced(boolean forced) {
676
677            m_forced = forced;
678        }
679
680    }
681
682    /**
683     * The meta mappings transformer.<p>
684     */
685    class MetaLookupTranformer implements Transformer {
686
687        /**
688         * @see org.apache.commons.collections.Transformer#transform(java.lang.Object)
689         */
690        public Object transform(Object arg0) {
691
692            String result = null;
693            if ((m_metaMappings != null) && m_metaMappings.containsKey(arg0)) {
694                MetaMapping mapping = m_metaMappings.get(arg0);
695                CmsGalleryNameMacroResolver resolver = null;
696                try {
697                    CmsResourceFilter filter = getIsEditMode()
698                    ? CmsResourceFilter.IGNORE_EXPIRATION
699                    : CmsResourceFilter.DEFAULT;
700                    CmsResource res = m_cms.readResource(mapping.m_contentId, filter);
701                    CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, res, m_request);
702                    resolver = new CmsGalleryNameMacroResolver(m_cms, content, getLocale());
703                    if (content.hasLocale(getLocale())) {
704                        I_CmsXmlContentValue val = content.getValue(mapping.m_elementXPath, getLocale());
705                        if (val != null) {
706                            result = val.getStringValue(m_cms);
707                        }
708                    }
709
710                } catch (CmsException e) {
711                    LOG.error(e.getLocalizedMessage(), e);
712                }
713                if (result == null) {
714                    result = mapping.m_defaultValue;
715                }
716                if ((resolver != null) && (result != null)) {
717                    result = resolver.resolveMacros(result);
718                }
719            }
720            return result;
721        }
722
723    }
724
725    /** The meta mapping data. */
726    class MetaMapping {
727
728        /** The mapping content structure id. */
729        CmsUUID m_contentId;
730
731        /** The default value. */
732        String m_defaultValue;
733
734        /** The mapping value xpath. */
735        String m_elementXPath;
736
737        /** The mapping key. */
738        String m_key;
739
740        /** The mapping order. */
741        int m_order;
742    }
743
744    /** The attribute name of the cms object.*/
745    public static final String ATTRIBUTE_CMS_OBJECT = "__cmsObject";
746
747    /** The attribute name of the standard JSP context bean. */
748    public static final String ATTRIBUTE_NAME = "cms";
749
750    /** The logger instance for this class. */
751    protected static final Log LOG = CmsLog.getLog(CmsJspStandardContextBean.class);
752
753    /** OpenCms user context. */
754    protected CmsObject m_cms;
755
756    /** The sitemap configuration. */
757    protected CmsADEConfigData m_config;
758
759    /** The meta mapping configuration. */
760    Map<String, MetaMapping> m_metaMappings;
761
762    /** The current request. */
763    ServletRequest m_request;
764
765    /** Lazily initialized map from a category path to all sub-categories of that category. */
766    private Map<String, CmsJspCategoryAccessBean> m_allSubCategories;
767
768    /** Lazily initialized nested map for reading either attributes or properties (first key: file name, second key: attribute / property name). */
769    private Map<String, Map<String, CmsJspObjectValueWrapper>> m_attributesOrProperties;
770
771    /** Lazily initialized map from a category path to the path's category object. */
772    private Map<String, CmsCategory> m_categories;
773
774    /** The container the currently rendered element is part of. */
775    private CmsContainerBean m_container;
776
777    /** The current detail content resource if available. */
778    private CmsResource m_detailContentResource;
779
780    /** The detail function page. */
781    private CmsResource m_detailFunctionPage;
782
783    /** The detail only page references containers that are only displayed in detail view. */
784    private CmsContainerPageBean m_detailOnlyPage;
785
786    /** Flag to indicate if element was just edited. */
787    private boolean m_edited;
788
789    /** The currently rendered element. */
790    private CmsContainerElementBean m_element;
791
792    /** The elements of the current page. */
793    private Map<String, CmsContainerElementBean> m_elementInstances;
794
795    /** Flag to force edit mode to be disabled. */
796    private boolean m_forceDisableEditMode;
797
798    /** The lazy initialized map which allows access to the dynamic function beans. */
799    private Map<String, CmsDynamicFunctionBeanWrapper> m_function;
800
801    /** The lazy initialized map for the function detail pages. */
802    private Map<String, String> m_functionDetailPage;
803
804    /** The lazy initialized map for the function detail pages. */
805    private Map<String, String> m_functionDetailPageExact;
806
807    /** Indicates if in drag mode. */
808    private boolean m_isDragMode;
809
810    /** Stores the edit mode info. */
811    private Boolean m_isEditMode;
812
813    /** Lazily initialized map from the locale to the localized title property. */
814    private Map<String, String> m_localeTitles;
815
816    /** The currently displayed container page. */
817    private CmsContainerPageBean m_page;
818
819    /** The current container page resource, lazy initialized. */
820    private CmsJspResourceWrapper m_pageResource;
821
822    /** The parent containers to the given element instance ids. */
823    private Map<String, CmsContainerBean> m_parentContainers;
824
825    /** Lazily initialized map from a category path to all categories on that path. */
826    private Map<String, List<CmsCategory>> m_pathCategories;
827
828    /** Lazily initialized map from the root path of a resource to all categories assigned to the resource. */
829    private Map<String, CmsJspCategoryAccessBean> m_resourceCategories;
830
831    /** Map from root paths to site relative paths. */
832    private Map<String, String> m_sitePaths;
833
834    /** The template plugins. */
835    private Map<String, List<CmsTemplatePluginWrapper>> m_templatePlugins;
836
837    /** The lazy initialized map for the detail pages. */
838    private Map<String, String> m_typeDetailPage;
839
840    /** The VFS content access bean. */
841    private CmsJspVfsAccessBean m_vfsBean;
842
843    /**
844     * Creates an empty instance.<p>
845     */
846    private CmsJspStandardContextBean() {
847
848    }
849
850    /**
851     * Creates a new standard JSP context bean.
852     *
853     * @param req the current servlet request
854     */
855    private CmsJspStandardContextBean(ServletRequest req) {
856
857        this();
858        CmsFlexController controller = CmsFlexController.getController(req);
859        m_request = req;
860        CmsObject cms;
861        if (controller != null) {
862            cms = controller.getCmsObject();
863        } else {
864            cms = (CmsObject)req.getAttribute(ATTRIBUTE_CMS_OBJECT);
865        }
866        if (cms == null) {
867            // cms object unavailable - this request was not initialized properly
868            throw new CmsRuntimeException(
869                Messages.get().container(Messages.ERR_MISSING_CMS_CONTROLLER_1, CmsJspBean.class.getName()));
870        }
871        updateCmsObject(cms);
872        m_detailContentResource = CmsDetailPageResourceHandler.getDetailResource(req);
873        m_detailFunctionPage = CmsDetailPageResourceHandler.getDetailFunctionPage(req);
874    }
875
876    /**
877     * Gets the link to a function detail page.
878     *
879     * @param cms the CMS context
880     * @param prefix the function detail prefix
881     * @param functionName the function name
882     * @param fullLink true if links should be generated with server prefix
883     *
884     * @return the link
885     */
886    public static String getFunctionDetailLink(CmsObject cms, String prefix, String functionName, boolean fullLink) {
887
888        String type = prefix + functionName;
889
890        CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
891            cms,
892            cms.addSiteRoot(cms.getRequestContext().getUri()));
893        List<CmsDetailPageInfo> detailPages = config.getDetailPagesForType(type);
894        CmsDetailPageInfo detailPage = null;
895        if ((detailPages == null) || (detailPages.size() == 0)) {
896            detailPage = config.getDefaultDetailPage();
897        } else {
898            detailPage = detailPages.get(0);
899        }
900        if (detailPage == null) {
901            return "[No detail page configured for type =" + type + "=]";
902        }
903
904        CmsUUID id = detailPage.getId();
905        try {
906            CmsResource r = cms.readResource(id);
907            boolean originalForceAbsoluteLinks = cms.getRequestContext().isForceAbsoluteLinks();
908            try {
909                cms.getRequestContext().setForceAbsoluteLinks(fullLink || originalForceAbsoluteLinks);
910                String link = OpenCms.getLinkManager().substituteLink(cms, r);
911                return link;
912            } finally {
913                cms.getRequestContext().setForceAbsoluteLinks(originalForceAbsoluteLinks);
914            }
915        } catch (CmsException e) {
916            LOG.warn(e.getLocalizedMessage(), e);
917            return "[Error reading detail page for type =" + type + "=]";
918        }
919    }
920
921    /**
922     * Gets the link to a function detail page.
923     *
924     * <p>This just returns null if no function detail page is defined, it does not use the default detail page as a fallback.
925     *
926     * @param cms the CMS context
927     * @param functionName the function name
928     *
929     * @return the link
930     */
931    public static String getFunctionDetailLinkExact(CmsObject cms, String functionName) {
932
933        String type = CmsDetailPageInfo.FUNCTION_PREFIX + functionName;
934
935        CmsADEConfigData config = OpenCms.getADEManager().lookupConfigurationWithCache(
936            cms,
937            cms.addSiteRoot(cms.getRequestContext().getUri()));
938        List<CmsDetailPageInfo> detailPages = config.getDetailPagesForType(type);
939
940        CmsDetailPageInfo detailPage = null;
941        if ((detailPages == null) || (detailPages.size() == 0)) {
942            return null;
943        }
944        detailPage = detailPages.get(0);
945        if (detailPage.isDefaultDetailPage()) {
946            return null;
947        }
948
949        CmsUUID id = detailPage.getId();
950        try {
951            CmsResource r = cms.readResource(id);
952            String link = OpenCms.getLinkManager().substituteLink(cms, r);
953            return link;
954        } catch (CmsException e) {
955            LOG.warn(e.getLocalizedMessage(), e);
956            return null;
957        }
958
959    }
960
961    /**
962     * Creates a new instance of the standard JSP context bean.<p>
963     *
964     * To prevent multiple creations of the bean during a request, the OpenCms request context
965     * attributes are used to cache the created VFS access utility bean.<p>
966     *
967     * @param req the current servlet request
968     *
969     * @return a new instance of the standard JSP context bean
970     */
971    public static CmsJspStandardContextBean getInstance(ServletRequest req) {
972
973        Object attribute = req.getAttribute(ATTRIBUTE_NAME);
974        CmsJspStandardContextBean result;
975        if ((attribute != null) && (attribute instanceof CmsJspStandardContextBean)) {
976            result = (CmsJspStandardContextBean)attribute;
977        } else {
978            result = new CmsJspStandardContextBean(req);
979            req.setAttribute(ATTRIBUTE_NAME, result);
980        }
981        return result;
982    }
983
984    /**
985     * Returns a copy of this JSP context bean.<p>
986     *
987     * @return a copy of this JSP context bean
988     */
989    public CmsJspStandardContextBean createCopy() {
990
991        CmsJspStandardContextBean result = new CmsJspStandardContextBean();
992        result.m_container = m_container;
993        if (m_detailContentResource != null) {
994            result.m_detailContentResource = m_detailContentResource.getCopy();
995        }
996        result.m_element = m_element;
997        result.m_forceDisableEditMode = m_forceDisableEditMode;
998        result.setPage(m_page);
999        return result;
1000    }
1001
1002    /**
1003     * Uses the default text encryption method to decrypt an encrypted string.
1004     *
1005     * @param text the encrypted stirng
1006     * @return the decrypted string
1007     */
1008    public String decrypt(String text) {
1009
1010        try {
1011            return OpenCms.getTextEncryptions().get("default").decrypt(text);
1012        } catch (Exception e) {
1013            return null;
1014        }
1015
1016    }
1017
1018    /**
1019     * Returns a caching hash specific to the element, it's properties and the current container width.<p>
1020     *
1021     * @return the caching hash
1022     */
1023    public String elementCachingHash() {
1024
1025        String result = "";
1026        if (m_element != null) {
1027            result = m_element.editorHash();
1028            if (m_container != null) {
1029                result += "w:"
1030                    + m_container.getWidth()
1031                    + "cName:"
1032                    + m_container.getName()
1033                    + "cType:"
1034                    + m_container.getType();
1035            }
1036        }
1037        return result;
1038    }
1039
1040    /**
1041     * Uses the default text encryption to encrypt an input text.
1042     *
1043     * @param text the input text
1044     * @return the encrypted text
1045     */
1046    public String encrypt(String text) {
1047
1048        try {
1049            return OpenCms.getTextEncryptions().get("default").encrypt(text);
1050        } catch (Exception e) {
1051            return null;
1052        }
1053    }
1054
1055    /**
1056     * Checks if the resource with the given path exists.
1057     *
1058     * @param path a path
1059     * @return true if the resource exists
1060     */
1061    public boolean exists(String path) {
1062
1063        Boolean exists = getVfs().getExists().get(path);
1064        return exists != null ? exists.booleanValue() : false;
1065
1066    }
1067
1068    /**
1069     * Returns the locales available for the currently requested URI.
1070     *
1071     * @return the locales available for the currently requested URI.
1072     */
1073    public List<Locale> getAvailableLocales() {
1074
1075        return OpenCms.getLocaleManager().getAvailableLocales(m_cms, getRequestContext().getUri());
1076    }
1077
1078    /**
1079     * Helper for easy instantiation and initialization of custom context beans that returns
1080     * an instance of the class specified via <code>className</code>, with the current context already set.
1081     *
1082     * @param className name of the class to instantiate. Must be a subclass of {@link A_CmsJspCustomContextBean}.
1083     * @return an instance of the provided class with the current context already set.
1084     */
1085    public Object getBean(String className) {
1086
1087        try {
1088            Class<?> clazz = Class.forName(className);
1089            if (A_CmsJspCustomContextBean.class.isAssignableFrom(clazz)) {
1090                Constructor<?> constructor = clazz.getConstructor();
1091                Object instance = constructor.newInstance();
1092                Method setContextMethod = clazz.getMethod("setContext", CmsJspStandardContextBean.class);
1093                setContextMethod.invoke(instance, this);
1094                return instance;
1095            } else {
1096                throw new Exception();
1097            }
1098        } catch (Exception e) {
1099            LOG.error(Messages.get().container(Messages.ERR_NO_CUSTOM_BEAN_1, className));
1100        }
1101        return null;
1102
1103    }
1104
1105    /**
1106     * Finds the folder to use for binary uploads, based on the list configuration given as an argument or
1107     * the current sitemap configuration.
1108     *
1109     * @param content the list configuration content
1110     *
1111     * @return the binary upload folder
1112     */
1113    public String getBinaryUploadFolder(CmsJspContentAccessBean content) {
1114
1115        String keyToFind = CmsADEConfigData.ATTR_BINARY_UPLOAD_TARGET;
1116        String baseValue = null;
1117        if (content != null) {
1118            for (CmsJspContentAccessValueWrapper wrapper : content.getValueList().get(
1119                CmsConfigParserUtils.N_PARAMETER)) {
1120                String paramKey = wrapper.getValue().get(CmsConfigParserUtils.N_KEY).getToString();
1121                String paramValue = wrapper.getValue().get(CmsConfigParserUtils.N_VALUE).getToString();
1122                if (paramKey.equals(keyToFind)) {
1123                    LOG.debug("Found upload folder in configuration: " + paramValue);
1124                    baseValue = paramValue;
1125                    break;
1126                }
1127            }
1128
1129            if (baseValue == null) {
1130                List<CmsJspContentAccessValueWrapper> folderEntries = content.getValueList().get(
1131                    CmsConfigParserUtils.N_SEARCH_FOLDER);
1132                if (folderEntries.size() == 1) {
1133                    CmsResource resource = folderEntries.get(0).getToResource();
1134                    List<String> galleryTypes = Arrays.asList(
1135                        CmsAjaxDownloadGallery.GALLERYTYPE_NAME,
1136                        CmsAjaxImageGallery.GALLERYTYPE_NAME);
1137                    if ((resource != null) && (null != findAncestor(m_cms, resource, (ancestor) -> {
1138                        return galleryTypes.stream().anyMatch(
1139                            type -> OpenCms.getResourceManager().matchResourceType(type, ancestor.getTypeId()));
1140                    }))) {
1141                        baseValue = m_cms.getSitePath(resource);
1142                        LOG.debug(
1143                            "Using single download gallery from search folder configuration as upload folder: "
1144                                + baseValue);
1145
1146                    }
1147                }
1148            }
1149        }
1150
1151        if (baseValue == null) {
1152            baseValue = m_config.getAttribute(keyToFind, null);
1153            if (baseValue != null) {
1154                LOG.debug("Found upload folder in sitemap configuration: " + baseValue);
1155            }
1156        }
1157
1158        CmsMacroResolver resolver = new CmsMacroResolver();
1159        resolver.setCmsObject(getCmsObject());
1160        resolver.addMacro("subsitepath", CmsFileUtil.removeTrailingSeparator(getSubSitePath()));
1161        resolver.addMacro("sitepath", "/");
1162
1163        // if baseValue is still null, then resolveMacros will just return null
1164        String result = resolver.resolveMacros(baseValue);
1165
1166        LOG.debug("Final value for upload folder : " + result);
1167        return result;
1168    }
1169
1170    /**
1171     * Generates a link to the bundle editor to edit the provided message key.
1172     * The back link for the editor is the current uri.
1173     *
1174     * If the bundle for the key could not be found, <code>null</code> is returned.
1175     *
1176     * @param messageKey the message key to open the bundle editor for.
1177     *
1178     * @return a link to the bundle editor for editing the provided key, or <code>null</code> if the bundle for the key could not be found.
1179     */
1180    public String getBundleEditorLink(String messageKey) {
1181
1182        return getBundleEditorLink(messageKey, null);
1183    }
1184
1185    /**
1186     * Generates a link to the bundle editor to edit the provided message key.
1187     * The back link for the editor is the current uri with the provided backLinkAnchor added as anchor..
1188     *
1189     * If the bundle for the key could not be found, <code>null</code> is returned.
1190     *
1191     * @param messageKey the message key to open the bundle editor for.
1192     * @param backLinkAnchor the anchor id to add to the backlink to the page. If <code>null</code> no anchor is added to the backlink.
1193     *
1194     * @return a link to the bundle editor for editing the provided key, or <code>null</code> if the bundle for the key could not be found.
1195     */
1196    public String getBundleEditorLink(String messageKey, String backLinkAnchor) {
1197
1198        return getBundleEditorLink(messageKey, backLinkAnchor, null);
1199    }
1200
1201    /**
1202     * Generates a link to the bundle editor to edit the provided message key.
1203     * The back link for the editor is the current uri with the provided backLinkAnchor added as anchor.
1204     *
1205     * If the bundle resource for the key could not be found, <code>null</code> is returned.
1206     *
1207     * @param messageKey the message key to open the bundle editor for.
1208     * @param backLinkAnchor the anchor id to add to the backlink to the page. If <code>null</code> no anchor is added to the backlink.
1209     * @param backLinkParams request parameters to add to the backlink without leading '?', e.g. "param1=a&param2=b".
1210     *
1211     * @return a link to the bundle editor for editing the provided key, or <code>null</code> if the bundle for the key could not be found.
1212     */
1213    public String getBundleEditorLink(String messageKey, String backLinkAnchor, String backLinkParams) {
1214
1215        return getBundleEditorLink(messageKey, backLinkAnchor, backLinkParams, null, null);
1216    }
1217
1218    /**
1219     * Generates a link to the bundle editor to edit the provided message key.
1220     * The back link for the editor is the current uri with the provided backLinkAnchor added as anchor.
1221     *
1222     * If the bundle resource for the key could not be found, <code>null</code> is returned.
1223     *
1224     * @param messageKey the message key to open the bundle editor for.
1225     * @param backLinkAnchor the anchor id to add to the backlink to the page. If <code>null</code> no anchor is added to the backlink.
1226     * @param backLinkParams request parameters to add to the backlink without leading '?', e.g. "param1=a&param2=b".
1227     * @param bundleFilters substrings of names of bundles to be preferred when multiple bundles contain the key.
1228     *
1229     * @return a link to the bundle editor for editing the provided key, or <code>null</code> if the bundle for the key could not be found.
1230     */
1231    public String getBundleEditorLink(
1232        String messageKey,
1233        String backLinkAnchor,
1234        String backLinkParams,
1235        List<String> bundleFilters) {
1236
1237        return getBundleEditorLink(messageKey, backLinkAnchor, backLinkParams, null, bundleFilters);
1238
1239    }
1240
1241    /**
1242     * Generates a link to the bundle editor to edit the provided message key.
1243     * The back link for the editor is the current uri with the provided backLinkAnchor added as anchor.
1244     *
1245     * If the bundle resource for the key could not be found, <code>null</code> is returned.
1246     *
1247     * @param messageKey the message key to open the bundle editor for.
1248     * @param backLinkAnchor the anchor id to add to the backlink to the page. If <code>null</code> no anchor is added to the backlink.
1249     * @param backLinkParams request parameters to add to the backlink without leading '?', e.g. "param1=a&param2=b".
1250     * @param bundleName the name of the bundle to search the key in. If <code>null</code> the bundle is detected automatically.
1251     *
1252     * @return a link to the bundle editor for editing the provided key, or <code>null</code> if the bundle for the key could not be found.
1253     */
1254    public String getBundleEditorLinkForBundle(
1255        String messageKey,
1256        String backLinkAnchor,
1257        String backLinkParams,
1258        String bundleName) {
1259
1260        return getBundleEditorLink(messageKey, backLinkAnchor, backLinkParams, bundleName, null);
1261
1262    }
1263
1264    /**
1265     * Gets the root path for the VFS-based message bundle containing the given message key.
1266     *
1267     * <p>If no VFS-based message bundle contains the given key, null is returned. If multiple message bundles contain it,
1268     * the name filters are applied in the given order until at least one bundle matches a filter.
1269     * If multiple bundles match, one of them is arbitrarily chosen (but a warning is logged).
1270     * If no bundle matches, an arbitrary bundle is chosen (but also a warning is logged).
1271     *
1272     * <p>Note: This uses the online (published) state of message bundles, so if you have unpublished bundle changes, they will not be reflected in
1273     * the result.
1274     *
1275     * @param messageKey the message key
1276     * @param bundleFilters substrings of names of bundles to be preferred when multiple bundles contain the key.
1277     * @return the root path of the bundle containing the message key
1278     */
1279    public String getBundleRootPath(String messageKey, List<String> bundleFilters) {
1280
1281        CmsObject cms = getCmsObject();
1282        try {
1283            CmsMessageToBundleIndex bundleIndex = null;
1284            OpenCmsServlet.RequestCache context = OpenCmsServlet.getRequestCache();
1285            if (context != null) {
1286                bundleIndex = (CmsMessageToBundleIndex)context.getAttribute(
1287                    CmsMessageToBundleIndex.class.getName() + "_" + cms.getRequestContext().getLocale(),
1288                    k -> {
1289                        try {
1290                            CmsMessageToBundleIndex result = CmsMessageToBundleIndex.read(getCmsObject());
1291                            return result;
1292                        } catch (Exception e) {
1293                            throw new RuntimeException(e);
1294                        }
1295                    });
1296
1297            } else {
1298                bundleIndex = CmsMessageToBundleIndex.read(getCmsObject());
1299            }
1300            Collection<String> bundles = bundleIndex.getBundlesPathForKey(messageKey);
1301            switch (bundles.size()) {
1302                case 0:
1303                    return null;
1304                case 1:
1305                    return bundles.iterator().next();
1306                default:
1307                    if (!((null == bundleFilters) || bundleFilters.isEmpty())) {
1308                        for (String filter : bundleFilters) {
1309                            Set<String> matchingBundles = new HashSet<>(bundles.size());
1310                            for (String bundle : bundles) {
1311                                if (bundle.contains(filter)) {
1312                                    matchingBundles.add(bundle);
1313                                }
1314                            }
1315                            if (matchingBundles.size() > 0) {
1316                                if (matchingBundles.size() > 1) {
1317                                    LOG.warn(
1318                                        "Ambiguous message bundle for key "
1319                                            + messageKey
1320                                            + " and filter "
1321                                            + filter
1322                                            + ":"
1323                                            + matchingBundles);
1324                                }
1325                                return matchingBundles.iterator().next();
1326                            }
1327                        }
1328                    }
1329                    LOG.warn("Ambiguous message bundle for key " + messageKey + ":" + bundles);
1330                    return bundles.iterator().next();
1331            }
1332        } catch (Exception e) {
1333            LOG.error(e.getLocalizedMessage(), e);
1334            return null;
1335        }
1336    }
1337
1338    /**
1339     * Returns the container the currently rendered element is part of.<p>
1340     *
1341     * @return the container the currently rendered element is part of
1342     */
1343    public CmsContainerBean getContainer() {
1344
1345        return m_container;
1346    }
1347
1348    /**
1349     * Gets information about a given container type.
1350     *
1351     * @param containerType the container type
1352     *
1353     * @return the bean with the information about the container type
1354     */
1355    public CmsContainerTypeInfoWrapper getContainerTypeInfo(String containerType) {
1356
1357        return new CmsContainerTypeInfoWrapper(this, m_cms, m_config, containerType);
1358    }
1359
1360    /**
1361     * Gets the CmsObject from the current Flex controller.
1362     *
1363     * @return the CmsObject from the current Flex controller
1364     */
1365    public CmsObject getControllerCms() {
1366
1367        return CmsFlexController.getController(m_request).getCmsObject();
1368    }
1369
1370    /**
1371     * Returns the current detail content, or <code>null</code> if no detail content is requested.<p>
1372     *
1373     * @return the current detail content, or <code>null</code> if no detail content is requested.<p>
1374     */
1375    public CmsJspResourceWrapper getDetailContent() {
1376
1377        return CmsJspResourceWrapper.wrap(m_cms, m_detailContentResource);
1378    }
1379
1380    /**
1381     * Returns the structure id of the current detail content, or <code>null</code> if no detail content is requested.<p>
1382     *
1383     * @return the structure id of the current detail content, or <code>null</code> if no detail content is requested.<p>
1384     */
1385    public CmsUUID getDetailContentId() {
1386
1387        return m_detailContentResource == null ? null : m_detailContentResource.getStructureId();
1388    }
1389
1390    /**
1391     * Returns the detail content site path, or <code>null</code> if not available.<p>
1392     *
1393     * @return the detail content site path
1394     */
1395    public String getDetailContentSitePath() {
1396
1397        return ((m_cms == null) || (m_detailContentResource == null))
1398        ? null
1399        : m_cms.getSitePath(m_detailContentResource);
1400    }
1401
1402    /**
1403     * Returns the detail function page.<p>
1404     *
1405     * @return the detail function page
1406     */
1407    public CmsJspResourceWrapper getDetailFunctionPage() {
1408
1409        return CmsJspResourceWrapper.wrap(m_cms, m_detailFunctionPage);
1410    }
1411
1412    /**
1413     * Returns the detail only page.<p>
1414     *
1415     * @return the detail only page
1416     */
1417    public CmsContainerPageBean getDetailOnlyPage() {
1418
1419        if ((null == m_detailOnlyPage) && (null != m_detailContentResource)) {
1420            String pageRootPath = m_cms.getRequestContext().addSiteRoot(m_cms.getRequestContext().getUri());
1421            m_detailOnlyPage = CmsDetailOnlyContainerUtil.getDetailOnlyPage(m_cms, m_request, pageRootPath, false);
1422        }
1423        return m_detailOnlyPage;
1424    }
1425
1426    /**
1427     * Returns the currently rendered element.<p>
1428     *
1429     * @return the currently rendered element
1430     */
1431    public CmsContainerElementWrapper getElement() {
1432
1433        return m_element != null ? new CmsContainerElementWrapper(m_element) : null;
1434    }
1435
1436    /**
1437     * Returns a lazy initialized map of wrapped container elements beans by container name suffix.<p>
1438     *
1439     * So in case there is more than one container where the name end with the given suffix,
1440     * a joined list of container elements beans is returned.<p>
1441     *
1442     * @return a lazy initialized map of wrapped container elements beans by container name suffix
1443     *
1444     * @see #getElementsInContainer()
1445     */
1446    public Map<String, List<CmsContainerElementWrapper>> getElementBeansInContainers() {
1447
1448        return CmsCollectionsGenericWrapper.createLazyMap(obj -> {
1449            if (obj instanceof String) {
1450                List<CmsContainerElementBean> containerElements = new ArrayList<>();
1451                for (CmsContainerBean container : getPage().getContainers().values()) {
1452                    if (container.getName().endsWith("-" + obj)) {
1453                        for (CmsContainerElementBean element : container.getElements()) {
1454                            try {
1455                                element.initResource(m_cms);
1456                                containerElements.add(new CmsContainerElementWrapper(element));
1457                            } catch (Exception e) {
1458                                LOG.error(e.getLocalizedMessage(), e);
1459                            }
1460                        }
1461                    }
1462                }
1463                return containerElements;
1464            } else {
1465                return null;
1466            }
1467        });
1468
1469    }
1470
1471    /**
1472     * Returns a lazy initialized map of wrapped element resources by container name.<p>
1473     *
1474     * @return the lazy map of element resource wrappers
1475     */
1476    public Map<String, List<CmsJspResourceWrapper>> getElementsInContainer() {
1477
1478        return CmsCollectionsGenericWrapper.createLazyMap(obj -> {
1479            if (obj instanceof String) {
1480                List<CmsJspResourceWrapper> elements = new ArrayList<>();
1481                CmsContainerBean container = getPage().getContainers().get(obj);
1482                if (container != null) {
1483                    for (CmsContainerElementBean element : container.getElements()) {
1484                        try {
1485                            element.initResource(m_cms);
1486                            elements.add(CmsJspResourceWrapper.wrap(m_cms, element.getResource()));
1487                        } catch (Exception e) {
1488                            LOG.error(e.getLocalizedMessage(), e);
1489                        }
1490                    }
1491                }
1492                return elements;
1493            } else {
1494                return null;
1495            }
1496        });
1497
1498    }
1499
1500    /**
1501     * Returns a lazy initialized map of wrapped element resources by container name suffix.<p>
1502     *
1503     * So in case there is more than one container where the name end with the given suffix,
1504     * a joined list of elements is returned.<p>
1505     *
1506     * @return the lazy map of element resource wrappers
1507     *
1508     * @see #getElementBeansInContainers()
1509     */
1510    public Map<String, List<CmsJspResourceWrapper>> getElementsInContainers() {
1511
1512        return CmsCollectionsGenericWrapper.createLazyMap(obj -> {
1513            if (obj instanceof String) {
1514                List<CmsJspResourceWrapper> elements = new ArrayList<>();
1515                for (CmsContainerBean container : getPage().getContainers().values()) {
1516                    if (container.getName().endsWith("-" + obj)) {
1517                        for (CmsContainerElementBean element : container.getElements()) {
1518                            try {
1519                                element.initResource(m_cms);
1520                                elements.add(CmsJspResourceWrapper.wrap(m_cms, element.getResource()));
1521                            } catch (Exception e) {
1522                                LOG.error(e.getLocalizedMessage(), e);
1523                            }
1524                        }
1525                    }
1526                }
1527                return elements;
1528            } else {
1529                return null;
1530            }
1531        });
1532
1533    }
1534
1535    /**
1536     * Alternative method name for getReloadMarker().
1537     *
1538     * @see org.opencms.jsp.util.CmsJspStandardContextBean#getReloadMarker()
1539     *
1540     * @return the reload marker
1541     */
1542    public String getEnableReload() {
1543
1544        return getReloadMarker();
1545    }
1546
1547    /**
1548     * Gets the formatter info wrapper for the given formatter key.
1549     *
1550     * @param formatterKey a formatter key
1551     * @return the formatter information for the formatter key, or null if no formatter was found
1552     */
1553    public CmsFormatterInfoWrapper getFormatterInfo(String formatterKey) {
1554
1555        CmsObject cms = m_cms;
1556        CmsADEConfigData config = m_config;
1557        I_CmsFormatterBean formatter = config.findFormatter(formatterKey);
1558        if (formatter == null) {
1559            return null;
1560        }
1561        return new CmsFormatterInfoWrapper(cms, config, formatter);
1562
1563    }
1564
1565    /**
1566     * Gets the formatter bean for active formatters with a given container type.
1567     *
1568     * @param containerType the container type
1569     * @return the wrapped formatters
1570     */
1571    public List<CmsFormatterInfoWrapper> getFormatterInfoForContainer(String containerType) {
1572
1573        return wrapFormatters(m_config.getActiveFormattersWithContainerType(containerType));
1574
1575    }
1576
1577    /**
1578     * Gets the formatter beans for active formatters with a given display type.
1579     *
1580     * @param displayType the display type
1581     * @return the wrapped formatters
1582     */
1583    public List<CmsFormatterInfoWrapper> getFormatterInfoForDisplay(String displayType) {
1584
1585        CmsADEConfigData config = m_config;
1586        return wrapFormatters(config.getActiveFormattersWithDisplayType(displayType));
1587    }
1588
1589    /**
1590     * Gets a lazy map which can be used to access element setting defaults for a specific formatter key and setting name.
1591     *
1592     * @return the lazy map
1593     */
1594    public Map<String, Map<String, CmsJspObjectValueWrapper>> getFormatterSettingDefault() {
1595
1596        return CmsCollectionsGenericWrapper.createLazyMap(input -> {
1597            String formatterKey = (String)input;
1598            I_CmsFormatterBean formatter = m_config.findFormatter(formatterKey);
1599            if (formatter == null) {
1600                return CmsCollectionsGenericWrapper.createLazyMap(input2 -> {
1601                    return CmsJspObjectValueWrapper.NULL_VALUE_WRAPPER;
1602                });
1603            } else {
1604                final Map<String, CmsXmlContentProperty> settingDefs = formatter.getSettings(m_config);
1605                return CmsCollectionsGenericWrapper.createLazyMap(input2 -> {
1606                    String settingName = (String)input2;
1607                    CmsXmlContentProperty settingDef = settingDefs.get(settingName);
1608                    if (settingDef == null) {
1609                        return CmsJspObjectValueWrapper.NULL_VALUE_WRAPPER;
1610                    } else {
1611                        String settingDefault = settingDef.getDefault();
1612                        return CmsJspObjectValueWrapper.createWrapper(m_cms, settingDefault);
1613                    }
1614                });
1615            }
1616        });
1617    }
1618
1619    /**
1620     * Returns a lazy initialized Map which allows access to the dynamic function beans using the JSP EL.<p>
1621     *
1622     * When given a key, the returned map will look up the corresponding dynamic function bean in the module configuration.<p>
1623     *
1624     * @return a lazy initialized Map which allows access to the dynamic function beans using the JSP EL
1625     */
1626    public Map<String, CmsDynamicFunctionBeanWrapper> getFunction() {
1627
1628        if (m_function == null) {
1629
1630            Transformer transformer = new Transformer() {
1631
1632                @Override
1633                public Object transform(Object input) {
1634
1635                    try {
1636                        CmsDynamicFunctionBean dynamicFunction = readDynamicFunctionBean((String)input);
1637                        CmsDynamicFunctionBeanWrapper wrapper = new CmsDynamicFunctionBeanWrapper(
1638                            m_cms,
1639                            dynamicFunction);
1640                        return wrapper;
1641
1642                    } catch (CmsException e) {
1643                        LOG.debug(e.getLocalizedMessage(), e);
1644                        return new CmsDynamicFunctionBeanWrapper(m_cms, null);
1645                    }
1646                }
1647            };
1648            m_function = CmsCollectionsGenericWrapper.createLazyMap(transformer);
1649        }
1650        return m_function;
1651
1652    }
1653
1654    /**
1655     * Deprecated method to access function detail pages using the EL.<p>
1656     *
1657     * @return a lazy initialized Map that provides the detail page link as a value when given the name of a
1658     * (named) dynamic function as a key
1659     *
1660     * @deprecated use {@link #getFunctionDetailPage()} instead
1661     */
1662    @Deprecated
1663    public Map<String, String> getFunctionDetail() {
1664
1665        return getFunctionDetailPage();
1666    }
1667
1668    /**
1669     * Returns a lazy initialized Map that provides the detail page link as a value when given the name of a
1670     * (named) dynamic function as a key.<p>
1671     *
1672     * The provided Map key is assumed to be a String that represents a named dynamic function.<p>
1673     *
1674     * Usage example on a JSP with the JSTL:<pre>
1675     * &lt;a href=${cms.functionDetailPage['search']} /&gt
1676     * </pre>
1677     *
1678     * @return a lazy initialized Map that provides the detail page link as a value when given the name of a
1679     * (named) dynamic function as a key
1680     *
1681     * @see #getTypeDetailPage()
1682     */
1683    public Map<String, String> getFunctionDetailPage() {
1684
1685        if (m_functionDetailPage == null) {
1686            m_functionDetailPage = CmsCollectionsGenericWrapper.createLazyMap(
1687                new CmsDetailLookupTransformer(CmsDetailPageInfo.FUNCTION_PREFIX));
1688        }
1689        return m_functionDetailPage;
1690    }
1691
1692    /**
1693     * Returns a lazy initialized Map that provides the detail page link as a value when given the name of a
1694     * (named) dynamic function as a key.<p>
1695     *
1696     * The provided Map key is assumed to be a String that represents a named dynamic function.<p>
1697     *
1698     * Usage example on a JSP with the JSTL:<pre>
1699     * &lt;a href=${cms.functionDetailPage['search']} /&gt
1700     * </pre>
1701     *
1702     * @return a lazy initialized Map that provides the detail page link as a value when given the name of a
1703     * (named) dynamic function as a key
1704     *
1705     * @see #getTypeDetailPage()
1706     */
1707    public Map<String, String> getFunctionDetailPageExact() {
1708
1709        if (m_functionDetailPageExact == null) {
1710            m_functionDetailPageExact = CmsCollectionsGenericWrapper.createLazyMap(
1711                name -> getFunctionDetailLinkExact(m_cms, (String)name));
1712        }
1713        return m_functionDetailPageExact;
1714    }
1715
1716    /**
1717     * Returns a lazy map which creates a wrapper object for a dynamic function format when given an XML content
1718     * as a key.<p>
1719     *
1720     * @return a lazy map for accessing function formats for a content
1721     */
1722    public Map<CmsJspContentAccessBean, CmsDynamicFunctionFormatWrapper> getFunctionFormatFromContent() {
1723
1724        Transformer transformer = new Transformer() {
1725
1726            @Override
1727            public Object transform(Object contentAccess) {
1728
1729                CmsXmlContent content = (CmsXmlContent)(((CmsJspContentAccessBean)contentAccess).getRawContent());
1730                CmsDynamicFunctionParser parser = new CmsDynamicFunctionParser();
1731                CmsDynamicFunctionBean functionBean = null;
1732                try {
1733                    functionBean = parser.parseFunctionBean(m_cms, content);
1734                } catch (CmsException e) {
1735                    LOG.debug(e.getLocalizedMessage(), e);
1736                    return new CmsDynamicFunctionFormatWrapper(m_cms, null);
1737                }
1738                String type = getContainer().getType();
1739                String width = getContainer().getWidth();
1740                int widthNum = -1;
1741                try {
1742                    widthNum = Integer.parseInt(width);
1743                } catch (NumberFormatException e) {
1744                    LOG.debug(e.getLocalizedMessage(), e);
1745                }
1746                CmsDynamicFunctionBean.Format format = functionBean.getFormatForContainer(m_cms, type, widthNum);
1747                CmsDynamicFunctionFormatWrapper wrapper = new CmsDynamicFunctionFormatWrapper(m_cms, format);
1748                return wrapper;
1749            }
1750        };
1751        return CmsCollectionsGenericWrapper.createLazyMap(transformer);
1752    }
1753
1754    /**
1755     * Returns <code>true</code> if the current page is a detail page.<p>
1756     *
1757     * @return <code>true</code> if the current page is a detail page
1758     */
1759    public boolean getIsDetailPage() {
1760
1761        CmsJspResourceWrapper page = getPageResource();
1762        return OpenCms.getADEManager().isDetailPage(m_cms, page);
1763    }
1764
1765    /**
1766     * Returns <code>true</code> if the current request is direct edit enabled.<p>
1767     *
1768     * Online-, history-requests, previews and temporary files will not be editable.<p>
1769     *
1770     * @return <code>true</code> if the current request is direct edit enabled
1771     */
1772    public boolean getIsEditMode() {
1773
1774        if (m_isEditMode == null) {
1775            m_isEditMode = Boolean.valueOf(CmsJspTagEditable.isEditableRequest(m_request));
1776        }
1777        return m_isEditMode.booleanValue() && !m_forceDisableEditMode;
1778    }
1779
1780    /**
1781     * Returns <code>true</code> if the current request is a JSON request.<p>
1782     *
1783     * @return <code>true</code> if we are in a JSON request
1784     */
1785    public boolean getIsJSONRequest() {
1786
1787        return CmsJsonPartFilter.isJsonRequest(m_request);
1788    }
1789
1790    /**
1791     * Returns <code>true</code> if the current project is the online project.<p>
1792     *
1793     * @return <code>true</code> if the current project is the online project
1794     */
1795    public boolean getIsOnlineProject() {
1796
1797        return m_cms.getRequestContext().getCurrentProject().isOnlineProject();
1798    }
1799
1800    /**
1801     * Returns true if the current request is in direct edit preview mode.<p>
1802     *
1803     * This is the case if the request is not in edit mode and in the online project.<p>
1804     *
1805     * @return <code>true</code> if the current request is in direct edit preview mode
1806     */
1807    public boolean getIsPreviewMode() {
1808
1809        return !getIsOnlineProject() && !getIsEditMode();
1810    }
1811
1812    /**
1813     * Returns the current locale.<p>
1814     *
1815     * @return the current locale
1816     */
1817    public Locale getLocale() {
1818
1819        return getRequestContext().getLocale();
1820    }
1821
1822    /**
1823     * Gets a map providing access to the locale variants of the current page.<p>
1824     *
1825     * Note that all available locales for the site / subsite are used as keys, not just the ones for which a locale
1826     * variant actually exists.
1827     *
1828     * Usage in JSPs: ${cms.localeResource['de']]
1829     *
1830     * @return the map from locale strings to locale variant resources
1831     */
1832    public Map<String, CmsJspResourceWrapper> getLocaleResource() {
1833
1834        Map<String, CmsJspResourceWrapper> result = getPageResource().getLocaleResource();
1835        List<Locale> locales = CmsLocaleGroupService.getPossibleLocales(m_cms, getPageResource());
1836        for (Locale locale : locales) {
1837            if (!result.containsKey(locale.toString())) {
1838                result.put(locale.toString(), null);
1839            }
1840        }
1841        return result;
1842    }
1843
1844    /**
1845     * Gets the main locale for the current page's locale group.<p>
1846     *
1847     * @return the main locale for the current page's locale group
1848     */
1849    public Locale getMainLocale() {
1850
1851        return getPageResource().getMainLocale();
1852    }
1853
1854    /**
1855     * Returns the meta mappings map.<p>
1856     *
1857     * @return the meta mappings
1858     */
1859    public Map<String, String> getMeta() {
1860
1861        initMetaMappings();
1862        return CmsCollectionsGenericWrapper.createLazyMap(new MetaLookupTranformer());
1863    }
1864
1865    /**
1866     * Returns the currently displayed container page.<p>
1867     *
1868     * @return the currently displayed container page
1869     */
1870    public CmsContainerPageBean getPage() {
1871
1872        if (null == m_page) {
1873            try {
1874                initPage();
1875            } catch (CmsException e) {
1876                if (LOG.isWarnEnabled()) {
1877                    LOG.warn(e, e);
1878                }
1879            }
1880        }
1881        return m_page;
1882    }
1883
1884    /**
1885     * Returns the container page bean for the give page and locale.<p>
1886     *
1887     * @param page the container page resource as id, path or already as resource
1888     * @param locale the content locale as locale or string
1889     *
1890     * @return the container page bean
1891     */
1892    public CmsContainerPageBean getPage(Object page, Object locale) {
1893
1894        CmsResource pageResource = null;
1895        CmsContainerPageBean result = null;
1896        if (m_cms != null) {
1897            try {
1898                pageResource = CmsJspElFunctions.convertRawResource(m_cms, page);
1899                Locale l = CmsJspElFunctions.convertLocale(locale);
1900                result = getPage(pageResource);
1901                if (result != null) {
1902                    CmsADEConfigData adeConfig = OpenCms.getADEManager().lookupConfiguration(
1903                        m_cms,
1904                        pageResource.getRootPath());
1905                    for (CmsContainerBean container : result.getContainers().values()) {
1906                        for (CmsContainerElementBean element : container.getElements()) {
1907                            boolean isGroupContainer = element.isGroupContainer(m_cms);
1908                            boolean isInheritedContainer = element.isInheritedContainer(m_cms);
1909                            I_CmsFormatterBean formatterConfig = null;
1910                            if (!isGroupContainer && !isInheritedContainer) {
1911                                element.initResource(m_cms);
1912                                // ensure that the formatter configuration id is added to the element settings, so it will be persisted on save
1913                                formatterConfig = CmsJspTagContainer.getFormatterConfigurationForElement(
1914                                    m_cms,
1915                                    element,
1916                                    adeConfig,
1917                                    container.getName(),
1918                                    "",
1919                                    0);
1920                                if (formatterConfig != null) {
1921                                    element.initSettings(m_cms, adeConfig, formatterConfig, l, m_request, null);
1922                                }
1923                            }
1924                        }
1925                    }
1926                }
1927            } catch (Exception e) {
1928                LOG.warn(e.getLocalizedMessage(), e);
1929            }
1930
1931        }
1932        return result;
1933    }
1934
1935    /**
1936     * Returns the current container page resource.<p>
1937     *
1938     * @return the current container page resource
1939     */
1940    public CmsJspResourceWrapper getPageResource() {
1941
1942        try {
1943            if (m_pageResource == null) {
1944                // get the container page itself, checking the history first
1945                m_pageResource = CmsJspResourceWrapper.wrap(
1946                    m_cms,
1947                    (CmsResource)CmsHistoryResourceHandler.getHistoryResource(m_request));
1948                if (m_pageResource == null) {
1949                    m_pageResource = CmsJspResourceWrapper.wrap(
1950                        m_cms,
1951                        m_cms.readResource(
1952                            m_cms.getRequestContext().getUri(),
1953                            CmsResourceFilter.ignoreExpirationOffline(m_cms)));
1954                }
1955            }
1956        } catch (CmsException e) {
1957            LOG.error(e.getLocalizedMessage(), e);
1958        }
1959        return m_pageResource;
1960    }
1961
1962    /**
1963     * Returns the parent container to the current container if available.<p>
1964     *
1965     * @return the parent container
1966     */
1967    public CmsContainerBean getParentContainer() {
1968
1969        CmsContainerBean result = null;
1970        if ((getContainer() != null) && (getContainer().getParentInstanceId() != null)) {
1971            result = m_parentContainers.get(getContainer().getParentInstanceId());
1972        }
1973        return result;
1974    }
1975
1976    /**
1977     * Returns the instance id parent container mapping.<p>
1978     *
1979     * @return the instance id parent container mapping
1980     */
1981    public Map<String, CmsContainerBean> getParentContainers() {
1982
1983        if (m_parentContainers == null) {
1984            initPageData();
1985        }
1986        return Collections.unmodifiableMap(m_parentContainers);
1987    }
1988
1989    /**
1990     * Returns the parent element to the current element if available.<p>
1991     *
1992     * @return the parent element or null
1993     */
1994    public CmsContainerElementBean getParentElement() {
1995
1996        return getParentElement(getElement());
1997    }
1998
1999    /**
2000     * Gets the set of plugin group names.
2001     *
2002     * @return the set of plugin group names
2003     */
2004    public Set<String> getPluginGroups() {
2005
2006        return getPlugins().keySet();
2007    }
2008
2009    /**
2010     * Gets the map of plugins by group.
2011     *
2012     * @return the map of active plugins by group
2013     */
2014    public Map<String, List<CmsTemplatePluginWrapper>> getPlugins() {
2015
2016        if (m_templatePlugins == null) {
2017            final Multimap<String, CmsTemplatePlugin> templatePluginsMultimap = new CmsTemplatePluginFinder(
2018                this).getTemplatePlugins();
2019            Map<String, List<CmsTemplatePluginWrapper>> templatePlugins = new HashMap<>();
2020            for (String key : templatePluginsMultimap.keySet()) {
2021                List<CmsTemplatePluginWrapper> wrappers = new ArrayList<>();
2022                for (CmsTemplatePlugin plugin : templatePluginsMultimap.get(key)) {
2023                    wrappers.add(new CmsTemplatePluginWrapper(m_cms, plugin));
2024                }
2025                templatePlugins.put(key, Collections.unmodifiableList(wrappers));
2026            }
2027            m_templatePlugins = templatePlugins;
2028        }
2029        return m_templatePlugins;
2030    }
2031
2032    /**
2033     * JSP EL accessor method for retrieving the preview formatters.<p>
2034     *
2035     * @return a lazy map for accessing preview formatters
2036     */
2037    public Map<String, String> getPreviewFormatter() {
2038
2039        Transformer transformer = new Transformer() {
2040
2041            @Override
2042            public Object transform(Object uri) {
2043
2044                try {
2045                    String rootPath = m_cms.getRequestContext().addSiteRoot((String)uri);
2046                    CmsResource resource = m_cms.readResource((String)uri);
2047                    CmsADEManager adeManager = OpenCms.getADEManager();
2048                    CmsADEConfigData configData = adeManager.lookupConfiguration(m_cms, rootPath);
2049                    CmsFormatterConfiguration formatterConfig = configData.getFormatters(m_cms, resource);
2050                    if (formatterConfig == null) {
2051                        return "";
2052                    }
2053                    I_CmsFormatterBean previewFormatter = formatterConfig.getPreviewFormatter();
2054                    if (previewFormatter == null) {
2055                        return "";
2056                    }
2057                    CmsUUID structureId = previewFormatter.getJspStructureId();
2058                    m_cms.readResource(structureId);
2059                    CmsResource formatterResource = m_cms.readResource(structureId);
2060                    String formatterSitePath = m_cms.getRequestContext().removeSiteRoot(
2061                        formatterResource.getRootPath());
2062                    return formatterSitePath;
2063                } catch (CmsException e) {
2064                    LOG.warn(e.getLocalizedMessage(), e);
2065                    return "";
2066                }
2067            }
2068        };
2069        return CmsCollectionsGenericWrapper.createLazyMap(transformer);
2070    }
2071
2072    /**
2073     * Reads all sub-categories below the provided category.
2074     * @return The map from the provided category to it's sub-categories in a {@link CmsJspCategoryAccessBean}.
2075     */
2076    public Map<String, CmsJspCategoryAccessBean> getReadAllSubCategories() {
2077
2078        if (null == m_allSubCategories) {
2079            m_allSubCategories = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2080
2081                @Override
2082                public Object transform(Object categoryPath) {
2083
2084                    try {
2085                        List<CmsCategory> categories = CmsCategoryService.getInstance().readCategories(
2086                            m_cms,
2087                            (String)categoryPath,
2088                            true,
2089                            m_cms.getRequestContext().getUri());
2090                        CmsJspCategoryAccessBean result = new CmsJspCategoryAccessBean(
2091                            m_cms,
2092                            categories,
2093                            (String)categoryPath);
2094                        return result;
2095                    } catch (CmsException e) {
2096                        LOG.warn(e.getLocalizedMessage(), e);
2097                        return null;
2098                    }
2099                }
2100
2101            });
2102        }
2103        return m_allSubCategories;
2104    }
2105
2106    /**
2107     * Lazily reads the given attribute from the current sitemap or a property of the same name from the given resource.
2108     *
2109     * <p>Usage example: ${cms.readAttributeOrProperty['/index.html']['attr']}
2110     *
2111     * @return a lazy loading map for accessing attributes / properties
2112     */
2113    public Map<String, Map<String, CmsJspObjectValueWrapper>> getReadAttributeOrProperty() {
2114
2115        if (m_attributesOrProperties == null) {
2116            m_attributesOrProperties = CmsCollectionsGenericWrapper.createLazyMap(pathObj -> {
2117                return CmsCollectionsGenericWrapper.createLazyMap(keyObj -> {
2118
2119                    String path = (String)pathObj;
2120                    String key = (String)keyObj;
2121
2122                    CmsObject cms = getCmsObject();
2123                    String result = m_config.getAttribute(key, null);
2124                    if (result == null) {
2125                        try {
2126                            CmsProperty prop = cms.readPropertyObject(path, key, /*search=*/true);
2127                            result = prop.getValue();
2128                        } catch (CmsVfsResourceNotFoundException e) {
2129                            LOG.info(e.getLocalizedMessage(), e);
2130                        } catch (Exception e) {
2131                            LOG.error(e.getLocalizedMessage(), e);
2132                        }
2133                    }
2134                    return CmsJspObjectValueWrapper.createWrapper(cms, result);
2135                });
2136            });
2137        }
2138        return m_attributesOrProperties;
2139    }
2140
2141    /**
2142     * Reads the categories assigned to the currently requested URI.
2143     * @return the categories assigned to the currently requested URI.
2144     */
2145    public CmsJspCategoryAccessBean getReadCategories() {
2146
2147        return getReadResourceCategories().get(getRequestContext().getRootUri());
2148    }
2149
2150    /**
2151     * Transforms the category path of a category to the category.
2152     * @return a map from root or site path to category.
2153     */
2154    public Map<String, CmsCategory> getReadCategory() {
2155
2156        if (null == m_categories) {
2157            m_categories = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2158
2159                public Object transform(Object categoryPath) {
2160
2161                    try {
2162                        CmsCategoryService catService = CmsCategoryService.getInstance();
2163                        return catService.localizeCategory(
2164                            m_cms,
2165                            catService.readCategory(m_cms, (String)categoryPath, getRequestContext().getUri()),
2166                            m_cms.getRequestContext().getLocale());
2167                    } catch (CmsException e) {
2168                        LOG.warn(e.getLocalizedMessage(), e);
2169                        return null;
2170                    }
2171                }
2172
2173            });
2174        }
2175        return m_categories;
2176    }
2177
2178    /**
2179     * Transforms the category path to the list of all categories on that path.<p>
2180     *
2181     * Example: For path <code>"location/europe/"</code>
2182     *          the list <code>[getReadCategory.get("location/"),getReadCategory.get("location/europe/")]</code>
2183     *          is returned.
2184     * @return a map from a category path to list of categories on that path.
2185     */
2186    public Map<String, List<CmsCategory>> getReadPathCategories() {
2187
2188        if (null == m_pathCategories) {
2189            m_pathCategories = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2190
2191                public Object transform(Object categoryPath) {
2192
2193                    List<CmsCategory> result = new ArrayList<CmsCategory>();
2194
2195                    String path = (String)categoryPath;
2196
2197                    if ((null == path) || (path.length() <= 1)) {
2198                        return result;
2199                    }
2200
2201                    //cut last slash
2202                    path = path.substring(0, path.length() - 1);
2203
2204                    List<String> pathParts = Arrays.asList(path.split("/"));
2205
2206                    String currentPath = "";
2207                    for (String part : pathParts) {
2208                        currentPath += part + "/";
2209                        CmsCategory category = getReadCategory().get(currentPath);
2210                        if (null != category) {
2211                            result.add(category);
2212                        }
2213                    }
2214                    return CmsCategoryService.getInstance().localizeCategories(
2215                        m_cms,
2216                        result,
2217                        m_cms.getRequestContext().getLocale());
2218                }
2219
2220            });
2221        }
2222        return m_pathCategories;
2223    }
2224
2225    /**
2226     * Reads the categories assigned to a resource.
2227     *
2228     * @return map from the resource path (root path) to the assigned categories
2229     */
2230    public Map<String, CmsJspCategoryAccessBean> getReadResourceCategories() {
2231
2232        if (null == m_resourceCategories) {
2233            m_resourceCategories = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2234
2235                public Object transform(Object resourceName) {
2236
2237                    try {
2238                        CmsResource resource = m_cms.readResource(
2239                            getRequestContext().removeSiteRoot((String)resourceName));
2240                        return new CmsJspCategoryAccessBean(m_cms, resource);
2241                    } catch (CmsException e) {
2242                        LOG.warn(e.getLocalizedMessage(), e);
2243                        return null;
2244                    }
2245                }
2246            });
2247        }
2248        return m_resourceCategories;
2249    }
2250
2251    /**
2252     * Returns a HTML comment string that will cause the container page editor to reload the page if the element or its settings
2253     * were edited.<p>
2254     *
2255     * @return the reload marker
2256     */
2257    public String getReloadMarker() {
2258
2259        if (m_cms.getRequestContext().getCurrentProject().isOnlineProject()) {
2260            return ""; // reload marker is not needed in Online mode
2261        } else {
2262            return CmsGwtConstants.FORMATTER_RELOAD_MARKER;
2263        }
2264    }
2265
2266    /**
2267     * Gets the stored request.
2268     *
2269     * @return the stored request
2270     */
2271    public ServletRequest getRequest() {
2272
2273        return m_request;
2274    }
2275
2276    /**
2277     * Returns the request context.<p>
2278     *
2279     * @return the request context
2280     */
2281    public CmsRequestContext getRequestContext() {
2282
2283        return m_cms.getRequestContext();
2284    }
2285
2286    /**
2287     * Gets information about a specific resource type for use in JSPs.
2288     *
2289     * <p>If no type with the given name exists, null is returned.
2290     *
2291     * @param typeName the type name
2292     * @return the bean representing the resource type
2293     */
2294    public CmsResourceTypeInfoWrapper getResourceTypeInfo(String typeName) {
2295
2296        try {
2297            I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(typeName);
2298            return new CmsResourceTypeInfoWrapper(this, m_cms, m_config, type);
2299        } catch (CmsLoaderException e) {
2300            LOG.info(e.getLocalizedMessage(), e);
2301            return null;
2302        }
2303    }
2304
2305    /**
2306     * Gets the schema information bean for the given type or XSD.
2307     *
2308     * @param typeOrXsd either the name of a resource type, or the VFS path to an XSD schema
2309     * @return the schema information bean
2310     *
2311     * @throws CmsException if something goes wrong
2312     */
2313    public CmsSchemaInfo getSchemaInfo(String typeOrXsd) throws CmsException {
2314
2315        CmsXmlContentDefinition contentDef = null;
2316        if (OpenCms.getResourceManager().hasResourceType(typeOrXsd)) {
2317            contentDef = CmsXmlContentDefinition.getContentDefinitionForType(m_cms, typeOrXsd);
2318        } else if (typeOrXsd.startsWith("/")) {
2319            contentDef = CmsXmlContentDefinition.unmarshal(m_cms, typeOrXsd);
2320        } else {
2321            throw new IllegalArgumentException("Invalid getSchemaInfo argument: " + typeOrXsd);
2322        }
2323        CmsSchemaInfo info = new CmsSchemaInfo(m_cms, contentDef);
2324        return info;
2325    }
2326
2327    /**
2328     * Returns the current site.<p>
2329     *
2330     * @return the current site
2331     */
2332    public CmsSite getSite() {
2333
2334        return OpenCms.getSiteManager().getSiteForSiteRoot(m_cms.getRequestContext().getSiteRoot());
2335    }
2336
2337    /**
2338     * Gets the wrapper for the sitemap configuration.
2339     *
2340     * @return the wrapper object for the sitemap configuration
2341     */
2342    public CmsJspSitemapConfigWrapper getSitemapConfig() {
2343
2344        return new CmsJspSitemapConfigWrapper(this);
2345    }
2346
2347    /**
2348     * Transforms root paths to site paths.
2349     *
2350     * @return lazy map from root paths to site paths.
2351     *
2352     * @see CmsRequestContext#removeSiteRoot(String)
2353     */
2354    public Map<String, String> getSitePath() {
2355
2356        if (m_sitePaths == null) {
2357            m_sitePaths = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2358
2359                public Object transform(Object rootPath) {
2360
2361                    if (rootPath instanceof String) {
2362                        return getRequestContext().removeSiteRoot((String)rootPath);
2363                    }
2364                    return null;
2365                }
2366            });
2367        }
2368        return m_sitePaths;
2369    }
2370
2371    /**
2372     * Returns the subsite path for the currently requested URI.<p>
2373     *
2374     * @return the subsite path
2375     */
2376    public String getSubSitePath() {
2377
2378        return m_cms.getRequestContext().removeSiteRoot(
2379            OpenCms.getADEManager().getSubSiteRoot(m_cms, m_cms.getRequestContext().getRootUri()));
2380    }
2381
2382    /**
2383     * Returns the system information.<p>
2384     *
2385     * @return the system information
2386     */
2387    public CmsSystemInfo getSystemInfo() {
2388
2389        return OpenCms.getSystemInfo();
2390    }
2391
2392    /**
2393     * Gets a bean containing information about the current template.<p>
2394     *
2395     * @return the template information bean
2396     */
2397    public TemplateBean getTemplate() {
2398
2399        TemplateBean templateBean = getRequestAttribute(CmsTemplateContextManager.ATTR_TEMPLATE_BEAN);
2400        if (templateBean == null) {
2401            templateBean = new TemplateBean("", "");
2402        }
2403        return templateBean;
2404    }
2405
2406    /**
2407     * Returns the title of a page delivered from OpenCms, usually used for the <code>&lt;title&gt;</code> tag of
2408     * a HTML page.<p>
2409     *
2410     * If no title information has been found, the empty String "" is returned.<p>
2411     *
2412     * @return the title of the current page
2413     */
2414    public String getTitle() {
2415
2416        return getLocaleSpecificTitle(null);
2417
2418    }
2419
2420    /**
2421     * Get the title and read the Title property according the provided locale.
2422     * @return The map from locales to the locale specific titles.
2423     */
2424    public Map<String, String> getTitleLocale() {
2425
2426        if (m_localeTitles == null) {
2427            m_localeTitles = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2428
2429                public Object transform(Object inputLocale) {
2430
2431                    Locale locale = null;
2432                    if (null != inputLocale) {
2433                        if (inputLocale instanceof Locale) {
2434                            locale = (Locale)inputLocale;
2435                        } else if (inputLocale instanceof String) {
2436                            try {
2437                                locale = LocaleUtils.toLocale((String)inputLocale);
2438                            } catch (IllegalArgumentException | NullPointerException e) {
2439                                // do nothing, just go on without locale
2440                            }
2441                        }
2442                    }
2443                    return getLocaleSpecificTitle(locale);
2444                }
2445
2446            });
2447        }
2448        return m_localeTitles;
2449    }
2450
2451    /**
2452     * Returns a lazy initialized Map that provides the detail page link as a value when given the name of a
2453     * resource type as a key.<p>
2454     *
2455     * The provided Map key is assumed to be the name of a resource type that has a detail page configured.<p>
2456     *
2457     * Usage example on a JSP with the JSTL:<pre>
2458     * &lt;a href=${cms.typeDetailPage['bs-blog']} /&gt
2459     * </pre>
2460     *
2461     * @return a lazy initialized Map that provides the detail page link as a value when given the name of a
2462     * resource type as a key
2463     *
2464     * @see #getFunctionDetailPage()
2465     */
2466    public Map<String, String> getTypeDetailPage() {
2467
2468        if (m_typeDetailPage == null) {
2469            m_typeDetailPage = CmsCollectionsGenericWrapper.createLazyMap(new CmsDetailLookupTransformer(""));
2470        }
2471        return m_typeDetailPage;
2472    }
2473
2474    /**
2475     * Returns an initialized VFS access bean.<p>
2476     *
2477     * @return an initialized VFS access bean
2478     */
2479    public CmsJspVfsAccessBean getVfs() {
2480
2481        if (m_vfsBean == null) {
2482            // create a new VVFS access bean
2483            m_vfsBean = CmsJspVfsAccessBean.create(m_cms);
2484        }
2485        return m_vfsBean;
2486    }
2487
2488    /**
2489     * Returns the workplace locale from the current user's settings.<p>
2490     *
2491     * @return returns the workplace locale from the current user's settings
2492     */
2493    public Locale getWorkplaceLocale() {
2494
2495        return OpenCms.getWorkplaceManager().getWorkplaceLocale(m_cms);
2496    }
2497
2498    /**
2499     * Returns an EL access wrapper map for the given object.<p>
2500     *
2501     * If the object is a {@link CmsResource}, then a {@link CmsJspResourceWrapper} is returned.
2502     * Otherwise the object is wrapped in a {@link CmsJspObjectValueWrapper}.<p>
2503     *
2504     * If the object is already is a wrapper, it is returned unchanged.<p>
2505     *
2506     * @return an EL access wrapper map for the given object
2507     */
2508    public Map<Object, Object> getWrap() {
2509
2510        return CmsCollectionsGenericWrapper.createLazyMap(obj -> wrap(obj));
2511    }
2512
2513    /**
2514     * Initializes the requested container page.<p>
2515     *
2516     * @throws CmsException in case reading the requested resource fails
2517     */
2518    public void initPage() throws CmsException {
2519
2520        if ((m_page == null) && (m_cms != null)) {
2521            String requestUri = m_cms.getRequestContext().getUri();
2522            // get the container page itself, checking the history first
2523            CmsResource pageResource = (CmsResource)CmsHistoryResourceHandler.getHistoryResource(m_request);
2524            if (pageResource == null) {
2525                pageResource = m_cms.readResource(requestUri, CmsResourceFilter.ignoreExpirationOffline(m_cms));
2526            }
2527            m_config = OpenCms.getADEManager().lookupConfigurationWithCache(m_cms, pageResource.getRootPath());
2528            m_page = getPage(pageResource);
2529            m_page = CmsTemplateMapper.get(m_request).transformContainerpageBean(
2530                m_cms,
2531                m_page,
2532                pageResource.getRootPath());
2533
2534        }
2535    }
2536
2537    /**
2538     * Returns <code>true</code in case a detail page is available for the current element.<p>
2539     *
2540     * @return <code>true</code in case a detail page is available for the current element
2541     */
2542    public boolean isDetailPageAvailable() {
2543
2544        boolean result = false;
2545        if ((m_cms != null)
2546            && (m_element != null)
2547            && !m_element.isInMemoryOnly()
2548            && (m_element.getResource() != null)) {
2549            try {
2550                String detailPage = OpenCms.getADEManager().getDetailPageHandler().getDetailPage(
2551                    m_cms,
2552                    m_element.getResource().getRootPath(),
2553                    m_cms.getRequestContext().getUri(),
2554                    null);
2555                result = detailPage != null;
2556            } catch (Exception e) {
2557                LOG.warn(e.getLocalizedMessage(), e);
2558            }
2559        }
2560        return result;
2561    }
2562
2563    /**
2564     * Returns <code>true</code> if this is a request to a detail resource, <code>false</code> otherwise.<p>
2565     *
2566     * Same as to check if {@link #getDetailContent()} is <code>null</code>.<p>
2567     *
2568     * @return <code>true</code> if this is a request to a detail resource, <code>false</code> otherwise
2569     */
2570    public boolean isDetailRequest() {
2571
2572        return m_detailContentResource != null;
2573    }
2574
2575    /**
2576     * Returns if the page is in drag mode.<p>
2577     *
2578     * @return if the page is in drag mode
2579     */
2580    public boolean isDragMode() {
2581
2582        return m_isDragMode;
2583    }
2584
2585    /**
2586     * Returns the flag to indicate if in drag and drop mode.<p>
2587     *
2588     * @return <code>true</code> if in drag and drop mode
2589     */
2590    public boolean isEdited() {
2591
2592        return m_edited;
2593    }
2594
2595    /**
2596     * Checks if the flag that forces edit mode to be disabled is set.
2597     *
2598     * @return true if the flag that disables edit mode is set
2599     */
2600    public boolean isForceDisableEditMode() {
2601
2602        return m_forceDisableEditMode;
2603    }
2604
2605    /**
2606     * Checks if the link is a link to a path in a different OpenCms site from the current one.
2607     *
2608     * @param link the link to check
2609     * @return true if the link is a link to different subsite
2610     */
2611    public boolean isLinkToDifferentSite(String link) {
2612
2613        CmsObject cms = getControllerCms();
2614        try {
2615            URI uri = new URI(link);
2616            if (uri.getScheme() != null) {
2617                String sitePart = uri.getScheme() + "://" + uri.getAuthority();
2618                CmsSiteMatcher matcher = new CmsSiteMatcher(sitePart);
2619                CmsSite site = OpenCms.getSiteManager().matchSite(matcher);
2620                return ((site != null) && !site.getSiteRoot().equals(cms.getRequestContext().getSiteRoot()));
2621            } else {
2622                return false;
2623            }
2624        } catch (URISyntaxException e) {
2625            return false;
2626        }
2627    }
2628
2629    /**
2630     * Checks if the link is a link to a path in a different OpenCms subsite from the current one.
2631     *
2632     * <p>For detail links, this checks the subsite of the detail page, not the subsite of the detail content.
2633     *
2634     * @param link the link to check
2635     * @return true if the link is a link to different site
2636     */
2637    public boolean isLinkToDifferentSubSite(String link) {
2638
2639        CmsObject cms = getControllerCms();
2640        String subSite = CmsLinkManager.getLinkSubsite(cms, link);
2641        String currentRootPath = cms.getRequestContext().addSiteRoot(cms.getRequestContext().getUri());
2642        boolean result = (subSite != null)
2643            && !subSite.equals(OpenCms.getADEManager().getSubSiteRoot(cms, currentRootPath));
2644        return result;
2645    }
2646
2647    /**
2648     * Returns if the current element is a model group.<p>
2649     *
2650     * @return <code>true</code> if the current element is a model group
2651     */
2652    public boolean isModelGroupElement() {
2653
2654        return (m_element != null) && !m_element.isInMemoryOnly() && isModelGroupPage() && m_element.isModelGroup();
2655    }
2656
2657    /**
2658     * Returns if the current page is used to manage model groups.<p>
2659     *
2660     * @return <code>true</code> if the current page is used to manage model groups
2661     */
2662    public boolean isModelGroupPage() {
2663
2664        CmsResource page = getPageResource();
2665        return (page != null) && CmsContainerpageService.isEditingModelGroups(m_cms, page);
2666
2667    }
2668
2669    /**
2670     * Gets the link wrapper for the given path.
2671     *
2672     * @param path the path
2673     * @return the link wrapper
2674     */
2675    public CmsJspLinkWrapper link(String path) {
2676
2677        return CmsJspObjectValueWrapper.createWrapper(m_cms, path).getToLink();
2678
2679    }
2680
2681    /**
2682     * Gets the resource wrapper for a given path or id.
2683     *
2684     * @param str a path or structure id
2685     * @return the wrapper for the resource with the given path or id
2686     */
2687    public CmsJspResourceWrapper readResource(String str) {
2688
2689        return getVfs().getReadResource().get(str);
2690
2691    }
2692
2693    /**
2694     * Reads an XML content and returns it as a content access bean
2695     * @param str path or id
2696     * @return the content access bean for the content with the given path or id
2697     */
2698    public CmsJspContentAccessBean readXml(String str) {
2699
2700        return getVfs().getReadXml().get(str);
2701    }
2702
2703    /**
2704     * Renders the elements of container in a container page wrapper as HTML (without a surrounding element).
2705     *
2706     * @param page the page wrapper
2707     * @param name the name or name prefix of the container
2708     * @return the rendered HTML
2709     */
2710    public String renderContainer(CmsJspContainerPageWrapper page, String name) {
2711
2712        String result = page.renderContainer(this, name);
2713        return result;
2714    }
2715
2716    /**
2717     * Sets the container the currently rendered element is part of.<p>
2718     *
2719     * @param container the container the currently rendered element is part of
2720     */
2721    public void setContainer(CmsContainerBean container) {
2722
2723        m_container = container;
2724    }
2725
2726    /**
2727     * Sets the detail only page.<p>
2728     *
2729     * @param detailOnlyPage the detail only page to set
2730     */
2731    public void setDetailOnlyPage(CmsContainerPageBean detailOnlyPage) {
2732
2733        m_detailOnlyPage = detailOnlyPage;
2734        clearPageData();
2735    }
2736
2737    /**
2738     * Sets if the page is in drag mode.<p>
2739     *
2740     * @param isDragMode if the page is in drag mode
2741     */
2742    public void setDragMode(boolean isDragMode) {
2743
2744        m_isDragMode = isDragMode;
2745    }
2746
2747    /**
2748     * Sets the flag to indicate if in drag and drop mode.<p>
2749     *
2750     * @param edited <code>true</code> if in drag and drop mode
2751     */
2752    public void setEdited(boolean edited) {
2753
2754        m_edited = edited;
2755    }
2756
2757    /**
2758     * In edit mode, creates a meta tag that tells the form-based content editor to use the stylesheet with the given path as a default.
2759     *
2760     * <p>Does nothing outside of edit mode.
2761     *
2762     * @param path the site path of a style sheet
2763     * @return the meta tag
2764     */
2765    public String setEditorCssPath(String path) {
2766
2767        if (getIsEditMode()) {
2768            return "\n<meta name=\""
2769                + CmsGwtConstants.META_EDITOR_STYLESHEET
2770                + "\" content=\""
2771                + CmsEncoder.escapeXml(path)
2772                + "\">\n";
2773        } else {
2774            return "";
2775        }
2776    }
2777
2778    /**
2779     * Sets the currently rendered element.<p>
2780     *
2781     * @param element the currently rendered element to set
2782     */
2783    public void setElement(CmsContainerElementBean element) {
2784
2785        m_element = element;
2786    }
2787
2788    /**
2789     * Enables / disables the flag that forces edit mode to be disabled.
2790     *
2791     * @param forceDisableEditMode the new value for the flag
2792     */
2793    public void setForceDisableEditMode(boolean forceDisableEditMode) {
2794
2795        m_forceDisableEditMode = forceDisableEditMode;
2796    }
2797
2798    /**
2799     * Sets the currently displayed container page.<p>
2800     *
2801     * @param page the currently displayed container page to set
2802     */
2803    public void setPage(CmsContainerPageBean page) {
2804
2805        m_page = page;
2806        clearPageData();
2807    }
2808
2809    /**
2810     * Converts the given object to a resource wrapper and returns it, or returns null if the conversion fails
2811     * @param obj the object to convert
2812     * @return the resource wrapper
2813     */
2814    public CmsJspResourceWrapper toResource(Object obj) {
2815
2816        Object wrapper = wrap(obj);
2817        try {
2818            if (obj instanceof A_CmsJspValueWrapper) {
2819                return ((A_CmsJspValueWrapper)obj).getToResource();
2820            } else if (obj instanceof CmsJspResourceWrapper) {
2821                return ((CmsJspResourceWrapper)obj).getToResource();
2822            } else {
2823                // in case we add another wrapper with a getToResource method that doesn't extend A_CmsJspValueWrapper
2824                return (CmsJspResourceWrapper)wrapper.getClass().getMethod("getToResource").invoke(wrapper);
2825            }
2826        } catch (Exception e) {
2827            LOG.debug(e.getLocalizedMessage(), e);
2828            return null;
2829        }
2830    }
2831
2832    /**
2833     * Updates the internally stored OpenCms user context.<p>
2834     *
2835     * @param cms the new OpenCms user context
2836     */
2837    public void updateCmsObject(CmsObject cms) {
2838
2839        try {
2840            m_cms = OpenCms.initCmsObject(cms);
2841        } catch (CmsException e) {
2842            LOG.error(e.getLocalizedMessage(), e);
2843            m_cms = cms;
2844        }
2845        try {
2846            m_config = OpenCms.getADEManager().lookupConfigurationWithCache(cms, cms.getRequestContext().getRootUri());
2847        } catch (Exception e) {
2848            LOG.error(e.getLocalizedMessage(), e);
2849        }
2850    }
2851
2852    /**
2853     * Updates the standard context bean from the request.<p>
2854     *
2855     * @param cmsFlexRequest the request from which to update the data
2856     */
2857    public void updateRequestData(CmsFlexRequest cmsFlexRequest) {
2858
2859        CmsResource detailRes = CmsDetailPageResourceHandler.getDetailResource(cmsFlexRequest);
2860        m_detailContentResource = detailRes;
2861        m_request = cmsFlexRequest;
2862    }
2863
2864    /**
2865     * Gets the path of either the detail content (if this is a detail request) or the current page if it's not a detail request.
2866     *
2867     * @return the URI of the page or detail content
2868     */
2869    public String uri() {
2870
2871        return isDetailRequest() ? getDetailContent().getSitePath() : getRequestContext().getUri();
2872    }
2873
2874    /**
2875     * Returns an EL access wrapper map for the given object.<p>
2876     *
2877     * If the object is a {@link CmsResource}, then a {@link CmsJspResourceWrapper} is returned.
2878     * Otherwise the object is wrapped in a {@link CmsJspObjectValueWrapper}.<p>
2879     *
2880     * If the object is already is a wrapper, it is returned unchanged.<p>
2881     *
2882     * @param obj the object to wrap
2883     * @return an EL access wrapper map for the given object
2884     */
2885    public Object wrap(Object obj) {
2886
2887        if ((obj instanceof A_CmsJspValueWrapper) || (obj instanceof CmsJspResourceWrapper)) {
2888            return obj;
2889        } else if (obj instanceof CmsResource) {
2890            return CmsJspResourceWrapper.wrap(m_cms, (CmsResource)obj);
2891        } else {
2892            return CmsJspObjectValueWrapper.createWrapper(m_cms, obj);
2893        }
2894    }
2895
2896    /**
2897     * Accessor for the CmsObject.
2898     *
2899     * @return the CmsObject
2900     */
2901    protected CmsObject getCmsObject() {
2902
2903        return m_cms;
2904    }
2905
2906    /**
2907     * Returns the formatter configuration to the given element.<p>
2908     *
2909     * @param element the element
2910     *
2911     * @return the formatter configuration
2912     */
2913    protected I_CmsFormatterBean getElementFormatter(CmsContainerElementBean element) {
2914
2915        if (m_elementInstances == null) {
2916            initPageData();
2917        }
2918        I_CmsFormatterBean formatter = null;
2919        CmsContainerBean container = m_parentContainers.get(element.getInstanceId());
2920        if (container == null) {
2921            // use the current container
2922            container = getContainer();
2923        }
2924        if (container != null) {
2925            String containerName = container.getName();
2926            Map<String, String> settings = element.getSettings();
2927            if (settings != null) {
2928                String formatterConfigId = settings.get(CmsFormatterConfig.getSettingsKeyForContainer(containerName));
2929                I_CmsFormatterBean dynamicFmt = m_config.findFormatter(formatterConfigId);
2930                if (dynamicFmt != null) {
2931                    formatter = dynamicFmt;
2932                }
2933            }
2934            if (formatter == null) {
2935                try {
2936                    CmsResource resource = m_cms.readResource(m_cms.getRequestContext().getUri());
2937
2938                    CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
2939                        m_cms,
2940                        resource.getRootPath());
2941                    CmsFormatterConfiguration formatters = config.getFormatters(m_cms, resource);
2942                    int width = -2;
2943                    try {
2944                        width = Integer.parseInt(container.getWidth());
2945                    } catch (Exception e) {
2946                        LOG.debug(e.getLocalizedMessage(), e);
2947                    }
2948                    formatter = formatters.getDefaultSchemaFormatter(container.getType(), width);
2949                } catch (CmsException e1) {
2950                    if (LOG.isWarnEnabled()) {
2951                        LOG.warn(e1.getLocalizedMessage(), e1);
2952                    }
2953                }
2954            }
2955        }
2956        return formatter;
2957    }
2958
2959    /**
2960     * Returns the title according to the given locale.
2961     * @param locale the locale for which the title should be read.
2962     * @return the title according to the given locale
2963     */
2964    protected String getLocaleSpecificTitle(Locale locale) {
2965
2966        String result = null;
2967
2968        try {
2969
2970            if (isDetailRequest()) {
2971                // this is a request to a detail page
2972                CmsResource res = getDetailContent();
2973                CmsFile file = m_cms.readFile(res);
2974                CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, file);
2975                result = content.getHandler().getTitleMapping(m_cms, content, m_cms.getRequestContext().getLocale());
2976                if (result == null) {
2977                    // title not found, maybe no mapping OR not available in the current locale
2978                    // read the title of the detail resource as fall back (may contain mapping from another locale)
2979                    result = m_cms.readPropertyObject(
2980                        res,
2981                        CmsPropertyDefinition.PROPERTY_TITLE,
2982                        false,
2983                        locale).getValue();
2984                }
2985            }
2986            if (result == null) {
2987                // read the title of the requested resource as fall back
2988                result = m_cms.readPropertyObject(
2989                    m_cms.getRequestContext().getUri(),
2990                    CmsPropertyDefinition.PROPERTY_TITLE,
2991                    true,
2992                    locale).getValue();
2993            }
2994        } catch (CmsException e) {
2995            LOG.debug(e.getLocalizedMessage(), e);
2996        }
2997        if (CmsStringUtil.isEmptyOrWhitespaceOnly(result)) {
2998            result = "";
2999        }
3000
3001        return result;
3002    }
3003
3004    /**
3005     * Returns the parent element if available.<p>
3006     *
3007     * @param element the element
3008     *
3009     * @return the parent element or null
3010     */
3011    protected CmsContainerElementBean getParentElement(CmsContainerElementBean element) {
3012
3013        if (m_elementInstances == null) {
3014            initPageData();
3015        }
3016        CmsContainerElementBean parent = null;
3017        CmsContainerBean cont = m_parentContainers.get(element.getInstanceId());
3018        if ((cont != null) && cont.isNestedContainer()) {
3019            parent = m_elementInstances.get(cont.getParentInstanceId());
3020        }
3021        return parent;
3022    }
3023
3024    /**
3025     * Accessor for the sitemap configuration.
3026     *
3027     * @return the sitemap configuration
3028     */
3029    protected CmsADEConfigData getSitemapConfigInternal() {
3030
3031        return m_config;
3032    }
3033
3034    /**
3035     * Reads a dynamic function bean, given its name in the module configuration.<p>
3036     *
3037     * @param configuredName the name of the dynamic function in the module configuration
3038     * @return the dynamic function bean for the dynamic function configured under that name
3039     *
3040     * @throws CmsException if something goes wrong
3041     */
3042    protected CmsDynamicFunctionBean readDynamicFunctionBean(String configuredName) throws CmsException {
3043
3044        CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
3045            m_cms,
3046            m_cms.addSiteRoot(m_cms.getRequestContext().getUri()));
3047        CmsFunctionReference functionRef = config.getFunctionReference(configuredName);
3048        if (functionRef == null) {
3049            return null;
3050        }
3051        CmsDynamicFunctionParser parser = new CmsDynamicFunctionParser();
3052        CmsResource functionResource = m_cms.readResource(functionRef.getStructureId());
3053        CmsDynamicFunctionBean result = parser.parseFunctionBean(m_cms, functionResource);
3054        return result;
3055    }
3056
3057    /**
3058     * Wraps a list of formatter beans for use in JSPs.
3059     *
3060     * @param formatters the formatters to wrap
3061     * @return the wrapped formatters
3062     */
3063    protected List<CmsFormatterInfoWrapper> wrapFormatters(Collection<? extends I_CmsFormatterBean> formatters) {
3064
3065        List<I_CmsFormatterBean> formattersToSort = new ArrayList<>(formatters);
3066        List<CmsResourceTypeConfig> types = m_config.getResourceTypes();
3067
3068        // we want to 'group' the returned formatters by resource type, which is slightly
3069        // complicated by the fact that formatters can support multiple resource types.
3070
3071        // first build a map that records the positions of the resource types configured in the sitemap configuration
3072        Map<String, Integer> typePositionsByTypeName = new HashMap<>();
3073        for (int i = 0; i < types.size(); i++) {
3074            CmsResourceTypeConfig singleType = types.get(i);
3075            typePositionsByTypeName.put(singleType.getTypeName(), Integer.valueOf(i));
3076        }
3077        // for each formatter, save the lowest position of any resource type it supports
3078        Map<String, Integer> lowestResourceTypePositionsByFormatterId = new HashMap<>();
3079        for (I_CmsFormatterBean formatter : formatters) {
3080            int pos = Integer.MAX_VALUE;
3081            for (String typeName : formatter.getResourceTypeNames()) {
3082                Integer typeOrder = typePositionsByTypeName.get(typeName);
3083                if (typeOrder != null) {
3084                    pos = Math.min(pos, typeOrder.intValue());
3085                }
3086            }
3087            lowestResourceTypePositionsByFormatterId.put(formatter.getId(), Integer.valueOf(pos));
3088        }
3089
3090        // now we can group the formatters by types using sorting, and inside the groups we sort by formatter rank
3091        Collections.sort(formattersToSort, new Comparator<I_CmsFormatterBean>() {
3092
3093            public int compare(I_CmsFormatterBean o1, I_CmsFormatterBean o2) {
3094
3095                return ComparisonChain.start().compare(getTypePosition(o1), getTypePosition(o2)).compare(
3096                    o2.getRank(),
3097                    o1.getRank()).result();
3098            }
3099
3100            public int getTypePosition(I_CmsFormatterBean formatter) {
3101
3102                return lowestResourceTypePositionsByFormatterId.computeIfAbsent(
3103                    formatter.getId(),
3104                    id -> Integer.valueOf(Integer.MAX_VALUE));
3105            }
3106
3107        });
3108        return formattersToSort.stream().map(
3109            formatter -> new CmsFormatterInfoWrapper(m_cms, m_config, formatter)).collect(Collectors.toList());
3110
3111    }
3112
3113    /**
3114     * Adds the mappings of the given formatter configuration.<p>
3115     *
3116     * @param formatterBean the formatter bean
3117     * @param elementId the element content structure id
3118     * @param resolver The macro resolver used on key and default value of the mappings
3119     * @param isDetailContent in case of a detail content
3120     */
3121    private void addMappingsForFormatter(
3122        I_CmsFormatterBean formatterBean,
3123        CmsUUID elementId,
3124        CmsMacroResolver resolver,
3125        boolean isDetailContent) {
3126
3127        if ((formatterBean != null) && (formatterBean.getMetaMappings() != null)) {
3128            for (CmsMetaMapping map : formatterBean.getMetaMappings()) {
3129                String key = map.getKey();
3130                key = resolver.resolveMacros(key);
3131                // the detail content mapping overrides other mappings
3132                if (isDetailContent
3133                    || !m_metaMappings.containsKey(key)
3134                    || (m_metaMappings.get(key).m_order <= map.getOrder())) {
3135                    MetaMapping mapping = new MetaMapping();
3136                    mapping.m_key = key;
3137                    mapping.m_elementXPath = map.getElement();
3138                    mapping.m_defaultValue = resolver.resolveMacros(map.getDefaultValue());
3139                    mapping.m_order = map.getOrder();
3140                    mapping.m_contentId = elementId;
3141                    m_metaMappings.put(key, mapping);
3142                }
3143            }
3144        }
3145    }
3146
3147    /**
3148     * Clears the page element data.<p>
3149     */
3150    private void clearPageData() {
3151
3152        m_elementInstances = null;
3153        m_parentContainers = null;
3154    }
3155
3156    /**
3157     * Finds the first ancestor of a resource matching a given predicate.
3158     *
3159     * @param cms the CMS context
3160     * @param resource the resource
3161     * @param predicate the predicate to test
3162     *
3163     * @return the first ancestor matching the predicate (which may possibly be the given resource itself), or null if no matching ancestor is found
3164     * @throws CmsException
3165     */
3166    private CmsResource findAncestor(CmsObject cms, CmsResource resource, Predicate<CmsResource> predicate) {
3167
3168        try {
3169            CmsObject rootCms = OpenCms.initCmsObject(cms);
3170            rootCms.getRequestContext().setSiteRoot("");
3171            CmsResource ancestor = resource;
3172            while (ancestor != null) {
3173                if (predicate.test(ancestor)) {
3174                    return ancestor;
3175                }
3176                String parentFolder = CmsResource.getParentFolder(ancestor.getRootPath());
3177                if (parentFolder == null) {
3178                    break;
3179                }
3180                try {
3181                    ancestor = rootCms.readResource(parentFolder, CmsResourceFilter.IGNORE_EXPIRATION);
3182                } catch (CmsException e) {
3183                    LOG.info(e.getLocalizedMessage(), e);
3184                    break;
3185                }
3186            }
3187        } catch (CmsException e) {
3188            LOG.error(e.getLocalizedMessage(), e);
3189        }
3190        return null;
3191    }
3192
3193    /**
3194     * Generates a link to the bundle editor to edit the provided message key.
3195     * The back link for the editor is the current uri with the provided backLinkAnchor added as anchor.
3196     *
3197     * If the bundle resource for the key could not be found, <code>null</code> is returned.
3198     *
3199     * @param messageKey the message key to open the bundle editor for.
3200     * @param backLinkAnchor the anchor id to add to the backlink to the page. If <code>null</code> no anchor is added to the backlink.
3201     * @param backLinkParams request parameters to add to the backlink without leading '?', e.g. "param1=a&param2=b".
3202     * @param bundleName the name of the bundle to search the key in. If <code>null</code> the bundle is detected automatically.
3203     * @param nameFilters if more than one bundle is matched, bundles that match (substring matching) at least one of the provided strings are preferred.
3204     *  This option is only useful, if the bundleName is not provided.
3205     *
3206     * @return a link to the bundle editor for editing the provided key, or <code>null</code> if the bundle for the key could not be found.
3207     */
3208    private String getBundleEditorLink(
3209        String messageKey,
3210        String backLinkAnchor,
3211        String backLinkParams,
3212        String bundleName,
3213        List<String> nameFilters) {
3214
3215        if (!m_cms.getRequestContext().getCurrentProject().isOnlineProject()) {
3216            String filePath = null;
3217            if (null == bundleName) {
3218                filePath = getBundleRootPath(messageKey, nameFilters);
3219            } else {
3220                ResourceBundle bundle = CmsResourceBundleLoader.getBundle(
3221                    bundleName,
3222                    m_cms.getRequestContext().getLocale());
3223                if (bundle instanceof CmsVfsResourceBundle) {
3224                    CmsVfsResourceBundle vfsBundle = (CmsVfsResourceBundle)bundle;
3225                    filePath = vfsBundle.getParameters().getBasePath();
3226                }
3227            }
3228            try {
3229                if (null == filePath) {
3230                    throw new Exception("Could not determine the VFS root path of the bundle.");
3231                }
3232                CmsUUID structureId = m_cms.readResource(
3233                    m_cms.getRequestContext().removeSiteRoot(filePath)).getStructureId();
3234                String backLink = OpenCms.getLinkManager().getServerLink(m_cms, m_cms.getRequestContext().getUri());
3235                if (!((null == backLinkParams) || backLinkParams.isEmpty())) {
3236                    backLink = backLink + "?" + backLinkParams;
3237                }
3238                if (!((null == backLinkAnchor) || backLinkAnchor.isEmpty())) {
3239                    backLink = backLink + "#" + backLinkAnchor;
3240                }
3241                String appState = CmsEditor.getEditState(structureId, false, backLink);
3242                if (null != messageKey) {
3243                    appState = A_CmsWorkplaceApp.addParamToState(
3244                        appState,
3245                        CmsMessageBundleEditor.PARAM_KEYFILTER,
3246                        messageKey);
3247                }
3248                String link = CmsVaadinUtils.getWorkplaceLink(CmsEditorConfiguration.APP_ID, appState);
3249                return link;
3250            } catch (Throwable t) {
3251                if (LOG.isWarnEnabled()) {
3252                    String message = "Failed to open bundle editor for key '"
3253                        + messageKey
3254                        + "' and bundle with name '"
3255                        + bundleName
3256                        + "'.";
3257                    if (LOG.isDebugEnabled()) {
3258                        LOG.debug(message, t);
3259                    } else {
3260                        LOG.warn(message);
3261                    }
3262                }
3263            }
3264        }
3265        return null;
3266    }
3267
3268    /**
3269     * Returns the container page bean for the give resource.<p>
3270     *
3271     * @param pageResource the resource
3272     *
3273     * @return the container page bean
3274     *
3275     * @throws CmsException in case reading the page bean fails
3276     */
3277    private CmsContainerPageBean getPage(CmsResource pageResource) throws CmsException {
3278
3279        CmsContainerPageBean result = null;
3280        if ((pageResource != null) && CmsResourceTypeXmlContainerPage.isContainerPage(pageResource)) {
3281            CmsXmlContainerPage xmlContainerPage = CmsXmlContainerPageFactory.unmarshal(m_cms, pageResource, m_request);
3282            result = xmlContainerPage.getContainerPage(m_cms);
3283            CmsModelGroupHelper modelHelper = new CmsModelGroupHelper(
3284                m_cms,
3285                OpenCms.getADEManager().lookupConfiguration(m_cms, pageResource.getRootPath()),
3286                CmsJspTagEditable.isEditableRequest(m_request) && (m_request instanceof HttpServletRequest)
3287                ? CmsADESessionCache.getCache((HttpServletRequest)m_request, m_cms)
3288                : null,
3289                CmsContainerpageService.isEditingModelGroups(m_cms, pageResource));
3290            result = modelHelper.readModelGroups(xmlContainerPage.getContainerPage(m_cms));
3291        }
3292        return result;
3293    }
3294
3295    /**
3296     * Convenience method for getting a request attribute without an explicit cast.<p>
3297     *
3298     * @param name the attribute name
3299     * @return the request attribute
3300     */
3301    @SuppressWarnings("unchecked")
3302    private <A> A getRequestAttribute(String name) {
3303
3304        Object attribute = m_request.getAttribute(name);
3305
3306        return attribute != null ? (A)attribute : null;
3307    }
3308
3309    /**
3310     * Initializes the mapping configuration.<p>
3311     */
3312    private void initMetaMappings() {
3313
3314        if (m_metaMappings == null) {
3315            m_metaMappings = new HashMap<String, MetaMapping>();
3316            try {
3317                initPage();
3318                CmsMacroResolver resolver = new CmsMacroResolver();
3319                resolver.setKeepEmptyMacros(true);
3320                resolver.setCmsObject(m_cms);
3321                resolver.setMessages(OpenCms.getWorkplaceManager().getMessages(getLocale()));
3322                CmsResourceFilter filter = getIsEditMode()
3323                ? CmsResourceFilter.IGNORE_EXPIRATION
3324                : CmsResourceFilter.DEFAULT;
3325                if (m_page != null) {
3326                    for (CmsContainerBean container : m_page.getContainers().values()) {
3327                        for (CmsContainerElementBean element : container.getElements()) {
3328                            String settingsKey = CmsFormatterConfig.getSettingsKeyForContainer(container.getName());
3329                            String formatterConfigId = element.getSettings() != null
3330                            ? element.getSettings().get(settingsKey)
3331                            : null;
3332                            I_CmsFormatterBean formatterBean = null;
3333                            formatterBean = m_config.findFormatter(formatterConfigId);
3334                            if ((formatterBean != null)
3335                                && formatterBean.useMetaMappingsForNormalElements()
3336                                && m_cms.existsResource(element.getId(), filter)) {
3337                                addMappingsForFormatter(formatterBean, element.getId(), resolver, false);
3338                            }
3339
3340                        }
3341                    }
3342                }
3343                if (getDetailContentId() != null) {
3344                    try {
3345                        CmsResource detailContent = m_cms.readResource(
3346                            getDetailContentId(),
3347                            CmsResourceFilter.ignoreExpirationOffline(m_cms));
3348                        CmsFormatterConfiguration config = OpenCms.getADEManager().lookupConfiguration(
3349                            m_cms,
3350                            m_cms.getRequestContext().getRootUri()).getFormatters(m_cms, detailContent);
3351                        for (I_CmsFormatterBean formatter : config.getDetailFormatters()) {
3352                            addMappingsForFormatter(formatter, getDetailContentId(), resolver, true);
3353                        }
3354                    } catch (CmsException e) {
3355                        LOG.error(
3356                            Messages.get().getBundle().key(
3357                                Messages.ERR_READING_REQUIRED_RESOURCE_1,
3358                                getDetailContentId()),
3359                            e);
3360                    }
3361                }
3362            } catch (Exception e) {
3363                LOG.error(e.getLocalizedMessage(), e);
3364            }
3365        }
3366    }
3367
3368    /**
3369     * Initializes the page element data.<p>
3370     */
3371    private void initPageData() {
3372
3373        m_elementInstances = new HashMap<String, CmsContainerElementBean>();
3374        m_parentContainers = new HashMap<String, CmsContainerBean>();
3375        if (m_page != null) {
3376            for (CmsContainerBean container : m_page.getContainers().values()) {
3377                for (CmsContainerElementBean element : container.getElements()) {
3378                    m_elementInstances.put(element.getInstanceId(), element);
3379                    m_parentContainers.put(element.getInstanceId(), container);
3380                    try {
3381                        if (element.isGroupContainer(m_cms) || element.isInheritedContainer(m_cms)) {
3382                            List<CmsContainerElementBean> children;
3383                            if (element.isGroupContainer(m_cms)) {
3384                                children = CmsJspTagContainer.getGroupContainerElements(
3385                                    m_cms,
3386                                    element,
3387                                    m_request,
3388                                    container.getType());
3389                            } else {
3390                                children = CmsJspTagContainer.getInheritedContainerElements(m_cms, element);
3391                            }
3392                            for (CmsContainerElementBean childElement : children) {
3393                                m_elementInstances.put(childElement.getInstanceId(), childElement);
3394                                m_parentContainers.put(childElement.getInstanceId(), container);
3395                            }
3396                        }
3397                    } catch (CmsException e) {
3398                        LOG.error(e.getLocalizedMessage(), e);
3399                    }
3400                }
3401            }
3402            // also add detail only data
3403            if (m_detailOnlyPage != null) {
3404                for (CmsContainerBean container : m_detailOnlyPage.getContainers().values()) {
3405                    for (CmsContainerElementBean element : container.getElements()) {
3406                        m_elementInstances.put(element.getInstanceId(), element);
3407                        m_parentContainers.put(element.getInstanceId(), container);
3408                    }
3409                }
3410            }
3411        }
3412    }
3413
3414}