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