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