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