001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.jsp.util;
029
030import org.opencms.ade.configuration.CmsADEConfigData;
031import org.opencms.ade.configuration.CmsADEManager;
032import org.opencms.ade.configuration.CmsFunctionReference;
033import org.opencms.ade.configuration.CmsResourceTypeConfig;
034import org.opencms.ade.configuration.plugins.CmsTemplatePlugin;
035import org.opencms.ade.configuration.plugins.CmsTemplatePluginFinder;
036import org.opencms.ade.containerpage.CmsContainerpageService;
037import org.opencms.ade.containerpage.CmsDetailOnlyContainerUtil;
038import org.opencms.ade.containerpage.CmsModelGroupHelper;
039import org.opencms.ade.containerpage.shared.CmsFormatterConfig;
040import org.opencms.ade.containerpage.shared.CmsInheritanceInfo;
041import org.opencms.ade.detailpage.CmsDetailPageInfo;
042import org.opencms.ade.detailpage.CmsDetailPageResourceHandler;
043import org.opencms.file.CmsFile;
044import org.opencms.file.CmsObject;
045import org.opencms.file.CmsProperty;
046import org.opencms.file.CmsPropertyDefinition;
047import org.opencms.file.CmsRequestContext;
048import org.opencms.file.CmsResource;
049import org.opencms.file.CmsResourceFilter;
050import org.opencms.file.CmsVfsResourceNotFoundException;
051import org.opencms.file.history.CmsHistoryResourceHandler;
052import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
053import org.opencms.file.types.I_CmsResourceType;
054import org.opencms.flex.CmsFlexController;
055import org.opencms.flex.CmsFlexRequest;
056import org.opencms.gwt.shared.CmsGwtConstants;
057import org.opencms.i18n.CmsEncoder;
058import org.opencms.i18n.CmsLocaleGroupService;
059import org.opencms.i18n.CmsMessageToBundleIndex;
060import org.opencms.i18n.CmsResourceBundleLoader;
061import org.opencms.i18n.CmsVfsResourceBundle;
062import org.opencms.jsp.CmsJspBean;
063import org.opencms.jsp.CmsJspResourceWrapper;
064import org.opencms.jsp.CmsJspTagContainer;
065import org.opencms.jsp.CmsJspTagEditable;
066import org.opencms.jsp.Messages;
067import org.opencms.jsp.jsonpart.CmsJsonPartFilter;
068import org.opencms.jsp.search.config.parser.simplesearch.CmsConfigParserUtils;
069import org.opencms.loader.CmsLoaderException;
070import org.opencms.loader.CmsTemplateContextManager;
071import org.opencms.main.CmsException;
072import org.opencms.main.CmsLog;
073import org.opencms.main.CmsRuntimeException;
074import org.opencms.main.CmsSystemInfo;
075import org.opencms.main.OpenCms;
076import org.opencms.main.OpenCmsServlet;
077import org.opencms.relations.CmsCategory;
078import org.opencms.relations.CmsCategoryService;
079import org.opencms.search.galleries.CmsGalleryNameMacroResolver;
080import org.opencms.site.CmsSite;
081import org.opencms.site.CmsSiteMatcher;
082import org.opencms.staticexport.CmsLinkManager;
083import org.opencms.ui.CmsVaadinUtils;
084import org.opencms.ui.apps.A_CmsWorkplaceApp;
085import org.opencms.ui.apps.CmsEditor;
086import org.opencms.ui.apps.CmsEditorConfiguration;
087import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditor;
088import org.opencms.util.CmsCollectionsGenericWrapper;
089import org.opencms.util.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     * Gets the set of plugin group names.
2056     *
2057     * @return the set of plugin group names
2058     */
2059    public Set<String> getPluginGroups() {
2060
2061        return getPlugins().keySet();
2062    }
2063
2064    /**
2065     * Gets the map of plugins by group.
2066     *
2067     * @return the map of active plugins by group
2068     */
2069    public Map<String, List<CmsTemplatePluginWrapper>> getPlugins() {
2070
2071        if (m_templatePlugins == null) {
2072            final Multimap<String, CmsTemplatePlugin> templatePluginsMultimap = new CmsTemplatePluginFinder(
2073                this).getTemplatePlugins();
2074            Map<String, List<CmsTemplatePluginWrapper>> templatePlugins = new HashMap<>();
2075            for (String key : templatePluginsMultimap.keySet()) {
2076                List<CmsTemplatePluginWrapper> wrappers = new ArrayList<>();
2077                for (CmsTemplatePlugin plugin : templatePluginsMultimap.get(key)) {
2078                    wrappers.add(new CmsTemplatePluginWrapper(m_cms, plugin));
2079                }
2080                templatePlugins.put(key, Collections.unmodifiableList(wrappers));
2081            }
2082            m_templatePlugins = templatePlugins;
2083        }
2084        return m_templatePlugins;
2085    }
2086
2087    /**
2088     * JSP EL accessor method for retrieving the preview formatters.<p>
2089     *
2090     * @return a lazy map for accessing preview formatters
2091     */
2092    public Map<String, String> getPreviewFormatter() {
2093
2094        Transformer transformer = new Transformer() {
2095
2096            @Override
2097            public Object transform(Object uri) {
2098
2099                try {
2100                    String rootPath = m_cms.getRequestContext().addSiteRoot((String)uri);
2101                    CmsResource resource = m_cms.readResource((String)uri);
2102                    CmsADEManager adeManager = OpenCms.getADEManager();
2103                    CmsADEConfigData configData = adeManager.lookupConfiguration(m_cms, rootPath);
2104                    CmsFormatterConfiguration formatterConfig = configData.getFormatters(m_cms, resource);
2105                    if (formatterConfig == null) {
2106                        return "";
2107                    }
2108                    I_CmsFormatterBean previewFormatter = formatterConfig.getPreviewFormatter();
2109                    if (previewFormatter == null) {
2110                        return "";
2111                    }
2112                    CmsUUID structureId = previewFormatter.getJspStructureId();
2113                    m_cms.readResource(structureId);
2114                    CmsResource formatterResource = m_cms.readResource(structureId);
2115                    String formatterSitePath = m_cms.getRequestContext().removeSiteRoot(
2116                        formatterResource.getRootPath());
2117                    return formatterSitePath;
2118                } catch (CmsException e) {
2119                    LOG.warn(e.getLocalizedMessage(), e);
2120                    return "";
2121                }
2122            }
2123        };
2124        return CmsCollectionsGenericWrapper.createLazyMap(transformer);
2125    }
2126
2127    /**
2128     * Gets the unwrapped element.
2129     *
2130     * @return the unwrapped element
2131     */
2132    public CmsContainerElementBean getRawElement() {
2133
2134        return m_element;
2135    }
2136
2137    /**
2138     * Reads all sub-categories below the provided category.
2139     * @return The map from the provided category to it's sub-categories in a {@link CmsJspCategoryAccessBean}.
2140     */
2141    public Map<String, CmsJspCategoryAccessBean> getReadAllSubCategories() {
2142
2143        if (null == m_allSubCategories) {
2144            m_allSubCategories = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2145
2146                @Override
2147                public Object transform(Object categoryPath) {
2148
2149                    try {
2150                        List<CmsCategory> categories = CmsCategoryService.getInstance().readCategories(
2151                            m_cms,
2152                            (String)categoryPath,
2153                            true,
2154                            m_cms.getRequestContext().getUri());
2155                        CmsJspCategoryAccessBean result = new CmsJspCategoryAccessBean(
2156                            m_cms,
2157                            categories,
2158                            (String)categoryPath);
2159                        return result;
2160                    } catch (CmsException e) {
2161                        LOG.warn(e.getLocalizedMessage(), e);
2162                        return null;
2163                    }
2164                }
2165
2166            });
2167        }
2168        return m_allSubCategories;
2169    }
2170
2171    /**
2172     * Lazily reads the given attribute from the current sitemap or a property of the same name from the given resource.
2173     *
2174     * <p>Usage example: ${cms.readAttributeOrProperty['/index.html']['attr']}
2175     *
2176     * @return a lazy loading map for accessing attributes / properties
2177     */
2178    public Map<String, Map<String, CmsJspObjectValueWrapper>> getReadAttributeOrProperty() {
2179
2180        if (m_attributesOrProperties == null) {
2181            m_attributesOrProperties = CmsCollectionsGenericWrapper.createLazyMap(pathObj -> {
2182                return CmsCollectionsGenericWrapper.createLazyMap(keyObj -> {
2183
2184                    String path = (String)pathObj;
2185                    String key = (String)keyObj;
2186
2187                    CmsObject cms = getCmsObject();
2188                    String result = m_config.getAttribute(key, null);
2189                    if (result == null) {
2190                        try {
2191                            CmsProperty prop = cms.readPropertyObject(path, key, /*search=*/true);
2192                            result = prop.getValue();
2193                        } catch (CmsVfsResourceNotFoundException e) {
2194                            LOG.info(e.getLocalizedMessage(), e);
2195                        } catch (Exception e) {
2196                            LOG.error(e.getLocalizedMessage(), e);
2197                        }
2198                    }
2199                    return CmsJspObjectValueWrapper.createWrapper(cms, result);
2200                });
2201            });
2202        }
2203        return m_attributesOrProperties;
2204    }
2205
2206    /**
2207     * Reads the categories assigned to the currently requested URI.
2208     * @return the categories assigned to the currently requested URI.
2209     */
2210    public CmsJspCategoryAccessBean getReadCategories() {
2211
2212        return getReadResourceCategories().get(getRequestContext().getRootUri());
2213    }
2214
2215    /**
2216     * Transforms the category path of a category to the category.
2217     * @return a map from root or site path to category.
2218     */
2219    public Map<String, CmsCategory> getReadCategory() {
2220
2221        if (null == m_categories) {
2222            m_categories = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2223
2224                public Object transform(Object categoryPath) {
2225
2226                    try {
2227                        CmsCategoryService catService = CmsCategoryService.getInstance();
2228                        return catService.localizeCategory(
2229                            m_cms,
2230                            catService.readCategory(m_cms, (String)categoryPath, getRequestContext().getUri()),
2231                            m_cms.getRequestContext().getLocale());
2232                    } catch (CmsException e) {
2233                        LOG.warn(e.getLocalizedMessage(), e);
2234                        return null;
2235                    }
2236                }
2237
2238            });
2239        }
2240        return m_categories;
2241    }
2242
2243    /**
2244     * Transforms the category path to the list of all categories on that path.<p>
2245     *
2246     * Example: For path <code>"location/europe/"</code>
2247     *          the list <code>[getReadCategory.get("location/"),getReadCategory.get("location/europe/")]</code>
2248     *          is returned.
2249     * @return a map from a category path to list of categories on that path.
2250     */
2251    public Map<String, List<CmsCategory>> getReadPathCategories() {
2252
2253        if (null == m_pathCategories) {
2254            m_pathCategories = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2255
2256                public Object transform(Object categoryPath) {
2257
2258                    List<CmsCategory> result = new ArrayList<CmsCategory>();
2259
2260                    String path = (String)categoryPath;
2261
2262                    if ((null == path) || (path.length() <= 1)) {
2263                        return result;
2264                    }
2265
2266                    //cut last slash
2267                    path = path.substring(0, path.length() - 1);
2268
2269                    List<String> pathParts = Arrays.asList(path.split("/"));
2270
2271                    String currentPath = "";
2272                    for (String part : pathParts) {
2273                        currentPath += part + "/";
2274                        CmsCategory category = getReadCategory().get(currentPath);
2275                        if (null != category) {
2276                            result.add(category);
2277                        }
2278                    }
2279                    return CmsCategoryService.getInstance().localizeCategories(
2280                        m_cms,
2281                        result,
2282                        m_cms.getRequestContext().getLocale());
2283                }
2284
2285            });
2286        }
2287        return m_pathCategories;
2288    }
2289
2290    /**
2291     * Reads the categories assigned to a resource.
2292     *
2293     * @return map from the resource path (root path) to the assigned categories
2294     */
2295    public Map<String, CmsJspCategoryAccessBean> getReadResourceCategories() {
2296
2297        if (null == m_resourceCategories) {
2298            m_resourceCategories = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2299
2300                public Object transform(Object resourceName) {
2301
2302                    try {
2303                        CmsResource resource = m_cms.readResource(
2304                            getRequestContext().removeSiteRoot((String)resourceName));
2305                        return new CmsJspCategoryAccessBean(m_cms, resource);
2306                    } catch (CmsException e) {
2307                        LOG.warn(e.getLocalizedMessage(), e);
2308                        return null;
2309                    }
2310                }
2311            });
2312        }
2313        return m_resourceCategories;
2314    }
2315
2316    /**
2317     * Returns a HTML comment string that will cause the container page editor to reload the page if the element or its settings
2318     * were edited.<p>
2319     *
2320     * @return the reload marker
2321     */
2322    public String getReloadMarker() {
2323
2324        if (m_cms.getRequestContext().getCurrentProject().isOnlineProject()) {
2325            return ""; // reload marker is not needed in Online mode
2326        } else {
2327            return CmsGwtConstants.FORMATTER_RELOAD_MARKER;
2328        }
2329    }
2330
2331    /**
2332     * Gets the stored request.
2333     *
2334     * @return the stored request
2335     */
2336    public ServletRequest getRequest() {
2337
2338        return m_request;
2339    }
2340
2341    /**
2342     * Returns the request context.<p>
2343     *
2344     * @return the request context
2345     */
2346    public CmsRequestContext getRequestContext() {
2347
2348        return m_cms.getRequestContext();
2349    }
2350
2351    /**
2352     * Gets information about a specific resource type for use in JSPs.
2353     *
2354     * <p>If no type with the given name exists, null is returned.
2355     *
2356     * @param typeName the type name
2357     * @return the bean representing the resource type
2358     */
2359    public CmsResourceTypeInfoWrapper getResourceTypeInfo(String typeName) {
2360
2361        try {
2362            I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(typeName);
2363            return new CmsResourceTypeInfoWrapper(this, m_cms, m_config, type);
2364        } catch (CmsLoaderException e) {
2365            LOG.info(e.getLocalizedMessage(), e);
2366            return null;
2367        }
2368    }
2369
2370    /**
2371     * Gets the schema information bean for the given type or XSD.
2372     *
2373     * @param typeOrXsd either the name of a resource type, or the VFS path to an XSD schema
2374     * @return the schema information bean
2375     *
2376     * @throws CmsException if something goes wrong
2377     */
2378    public CmsSchemaInfo getSchemaInfo(String typeOrXsd) throws CmsException {
2379
2380        CmsXmlContentDefinition contentDef = null;
2381        if (OpenCms.getResourceManager().hasResourceType(typeOrXsd)) {
2382            contentDef = CmsXmlContentDefinition.getContentDefinitionForType(m_cms, typeOrXsd);
2383        } else if (typeOrXsd.startsWith("/")) {
2384            contentDef = CmsXmlContentDefinition.unmarshal(m_cms, typeOrXsd);
2385        } else {
2386            throw new IllegalArgumentException("Invalid getSchemaInfo argument: " + typeOrXsd);
2387        }
2388        CmsSchemaInfo info = new CmsSchemaInfo(m_cms, contentDef);
2389        return info;
2390    }
2391
2392    /**
2393     * Returns the current site.<p>
2394     *
2395     * @return the current site
2396     */
2397    public CmsSite getSite() {
2398
2399        return OpenCms.getSiteManager().getSiteForSiteRoot(m_cms.getRequestContext().getSiteRoot());
2400    }
2401
2402    /**
2403     * Gets the wrapper for the sitemap configuration.
2404     *
2405     * @return the wrapper object for the sitemap configuration
2406     */
2407    public CmsJspSitemapConfigWrapper getSitemapConfig() {
2408
2409        return new CmsJspSitemapConfigWrapper(this);
2410    }
2411
2412    /**
2413     * Transforms root paths to site paths.
2414     *
2415     * @return lazy map from root paths to site paths.
2416     *
2417     * @see CmsRequestContext#removeSiteRoot(String)
2418     */
2419    public Map<String, String> getSitePath() {
2420
2421        if (m_sitePaths == null) {
2422            m_sitePaths = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2423
2424                public Object transform(Object rootPath) {
2425
2426                    if (rootPath instanceof String) {
2427                        return getRequestContext().removeSiteRoot((String)rootPath);
2428                    }
2429                    return null;
2430                }
2431            });
2432        }
2433        return m_sitePaths;
2434    }
2435
2436    /**
2437     * Returns the subsite path for the currently requested URI.<p>
2438     *
2439     * @return the subsite path
2440     */
2441    public String getSubSitePath() {
2442
2443        return m_cms.getRequestContext().removeSiteRoot(
2444            OpenCms.getADEManager().getSubSiteRoot(m_cms, m_cms.getRequestContext().getRootUri()));
2445    }
2446
2447    /**
2448     * Returns the system information.<p>
2449     *
2450     * @return the system information
2451     */
2452    public CmsSystemInfo getSystemInfo() {
2453
2454        return OpenCms.getSystemInfo();
2455    }
2456
2457    /**
2458     * Gets a bean containing information about the current template.<p>
2459     *
2460     * @return the template information bean
2461     */
2462    public TemplateBean getTemplate() {
2463
2464        TemplateBean templateBean = getRequestAttribute(CmsTemplateContextManager.ATTR_TEMPLATE_BEAN);
2465        if (templateBean == null) {
2466            templateBean = new TemplateBean("", "");
2467        }
2468        return templateBean;
2469    }
2470
2471    /**
2472     * Returns the title of a page delivered from OpenCms, usually used for the <code>&lt;title&gt;</code> tag of
2473     * a HTML page.<p>
2474     *
2475     * If no title information has been found, the empty String "" is returned.<p>
2476     *
2477     * @return the title of the current page
2478     */
2479    public String getTitle() {
2480
2481        return getLocaleSpecificTitle(null);
2482
2483    }
2484
2485    /**
2486     * Get the title and read the Title property according the provided locale.
2487     * @return The map from locales to the locale specific titles.
2488     */
2489    public Map<String, String> getTitleLocale() {
2490
2491        if (m_localeTitles == null) {
2492            m_localeTitles = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() {
2493
2494                public Object transform(Object inputLocale) {
2495
2496                    Locale locale = null;
2497                    if (null != inputLocale) {
2498                        if (inputLocale instanceof Locale) {
2499                            locale = (Locale)inputLocale;
2500                        } else if (inputLocale instanceof String) {
2501                            try {
2502                                locale = LocaleUtils.toLocale((String)inputLocale);
2503                            } catch (IllegalArgumentException | NullPointerException e) {
2504                                // do nothing, just go on without locale
2505                            }
2506                        }
2507                    }
2508                    return getLocaleSpecificTitle(locale);
2509                }
2510
2511            });
2512        }
2513        return m_localeTitles;
2514    }
2515
2516    /**
2517     * Returns a lazy initialized Map that provides the detail page link as a value when given the name of a
2518     * resource type as a key.<p>
2519     *
2520     * The provided Map key is assumed to be the name of a resource type that has a detail page configured.<p>
2521     *
2522     * Usage example on a JSP with the JSTL:<pre>
2523     * &lt;a href=${cms.typeDetailPage['bs-blog']} /&gt
2524     * </pre>
2525     *
2526     * @return a lazy initialized Map that provides the detail page link as a value when given the name of a
2527     * resource type as a key
2528     *
2529     * @see #getFunctionDetailPage()
2530     */
2531    public Map<String, String> getTypeDetailPage() {
2532
2533        if (m_typeDetailPage == null) {
2534            m_typeDetailPage = CmsCollectionsGenericWrapper.createLazyMap(new CmsDetailLookupTransformer(""));
2535        }
2536        return m_typeDetailPage;
2537    }
2538
2539    /**
2540     * Returns an initialized VFS access bean.<p>
2541     *
2542     * @return an initialized VFS access bean
2543     */
2544    public CmsJspVfsAccessBean getVfs() {
2545
2546        if (m_vfsBean == null) {
2547            // create a new VVFS access bean
2548            m_vfsBean = CmsJspVfsAccessBean.create(m_cms);
2549        }
2550        return m_vfsBean;
2551    }
2552
2553    /**
2554     * Returns the workplace locale from the current user's settings.<p>
2555     *
2556     * @return returns the workplace locale from the current user's settings
2557     */
2558    public Locale getWorkplaceLocale() {
2559
2560        return OpenCms.getWorkplaceManager().getWorkplaceLocale(m_cms);
2561    }
2562
2563    /**
2564     * Returns an EL access wrapper map for the given object.<p>
2565     *
2566     * If the object is a {@link CmsResource}, then a {@link CmsJspResourceWrapper} is returned.
2567     * Otherwise the object is wrapped in a {@link CmsJspObjectValueWrapper}.<p>
2568     *
2569     * If the object is already is a wrapper, it is returned unchanged.<p>
2570     *
2571     * @return an EL access wrapper map for the given object
2572     */
2573    public Map<Object, Object> getWrap() {
2574
2575        return CmsCollectionsGenericWrapper.createLazyMap(obj -> wrap(obj));
2576    }
2577
2578    /**
2579     * Initializes the requested container page.<p>
2580     *
2581     * @throws CmsException in case reading the requested resource fails
2582     */
2583    public void initPage() throws CmsException {
2584
2585        if ((m_page == null) && (m_cms != null)) {
2586            String requestUri = m_cms.getRequestContext().getUri();
2587            // get the container page itself, checking the history first
2588            CmsResource pageResource = (CmsResource)CmsHistoryResourceHandler.getHistoryResource(m_request);
2589            if (pageResource == null) {
2590                pageResource = m_cms.readResource(requestUri, CmsResourceFilter.ignoreExpirationOffline(m_cms));
2591            }
2592            m_config = OpenCms.getADEManager().lookupConfigurationWithCache(m_cms, pageResource.getRootPath());
2593            m_page = getPage(m_config, pageResource);
2594            m_page = CmsTemplateMapper.get(m_request).transformContainerpageBean(
2595                m_cms,
2596                m_page,
2597                pageResource.getRootPath());
2598
2599        }
2600    }
2601
2602    /**
2603     * Returns <code>true</code in case a detail page is available for the current element.<p>
2604     *
2605     * @return <code>true</code in case a detail page is available for the current element
2606     */
2607    public boolean isDetailPageAvailable() {
2608
2609        boolean result = false;
2610        if ((m_cms != null)
2611            && (m_element != null)
2612            && !m_element.isInMemoryOnly()
2613            && (m_element.getResource() != null)) {
2614            try {
2615                String detailPage = OpenCms.getADEManager().getDetailPageHandler().getDetailPage(
2616                    m_cms,
2617                    m_element.getResource().getRootPath(),
2618                    m_cms.getRequestContext().getUri(),
2619                    null);
2620                result = detailPage != null;
2621            } catch (Exception e) {
2622                LOG.warn(e.getLocalizedMessage(), e);
2623            }
2624        }
2625        return result;
2626    }
2627
2628    /**
2629     * Returns <code>true</code> if this is a request to a detail resource, <code>false</code> otherwise.<p>
2630     *
2631     * Same as to check if {@link #getDetailContent()} is <code>null</code>.<p>
2632     *
2633     * @return <code>true</code> if this is a request to a detail resource, <code>false</code> otherwise
2634     */
2635    public boolean isDetailRequest() {
2636
2637        return m_detailContentResource != null;
2638    }
2639
2640    /**
2641     * Returns if the page is in drag mode.<p>
2642     *
2643     * @return if the page is in drag mode
2644     */
2645    public boolean isDragMode() {
2646
2647        return m_isDragMode;
2648    }
2649
2650    /**
2651     * Returns the flag to indicate if in drag and drop mode.<p>
2652     *
2653     * @return <code>true</code> if in drag and drop mode
2654     */
2655    public boolean isEdited() {
2656
2657        return m_edited;
2658    }
2659
2660    /**
2661     * Checks if the flag that forces edit mode to be disabled is set.
2662     *
2663     * @return true if the flag that disables edit mode is set
2664     */
2665    public boolean isForceDisableEditMode() {
2666
2667        return m_forceDisableEditMode;
2668    }
2669
2670    /**
2671     * Checks if the link is a link to a path in a different OpenCms site from the current one.
2672     *
2673     * @param link the link to check
2674     * @return true if the link is a link to different subsite
2675     */
2676    public boolean isLinkToDifferentSite(String link) {
2677
2678        CmsObject cms = getControllerCms();
2679        try {
2680            URI uri = new URI(link);
2681            if (uri.getScheme() != null) {
2682                String sitePart = uri.getScheme() + "://" + uri.getAuthority();
2683                CmsSiteMatcher matcher = new CmsSiteMatcher(sitePart);
2684                CmsSite site = OpenCms.getSiteManager().matchSite(matcher);
2685                return ((site != null) && !site.getSiteRoot().equals(cms.getRequestContext().getSiteRoot()));
2686            } else {
2687                return false;
2688            }
2689        } catch (URISyntaxException e) {
2690            return false;
2691        }
2692    }
2693
2694    /**
2695     * Checks if the link is a link to a path in a different OpenCms subsite from the current one.
2696     *
2697     * <p>For detail links, this checks the subsite of the detail page, not the subsite of the detail content.
2698     *
2699     * @param link the link to check
2700     * @return true if the link is a link to different site
2701     */
2702    public boolean isLinkToDifferentSubSite(String link) {
2703
2704        CmsObject cms = getControllerCms();
2705        String subSite = CmsLinkManager.getLinkSubsite(cms, link);
2706        String currentRootPath = cms.getRequestContext().addSiteRoot(cms.getRequestContext().getUri());
2707        boolean result = (subSite != null)
2708            && !subSite.equals(OpenCms.getADEManager().getSubSiteRoot(cms, currentRootPath));
2709        return result;
2710    }
2711
2712    /**
2713     * Returns if the current element is a model group.<p>
2714     *
2715     * @return <code>true</code> if the current element is a model group
2716     */
2717    public boolean isModelGroupElement() {
2718
2719        return (m_element != null) && !m_element.isInMemoryOnly() && isModelGroupPage() && m_element.isModelGroup();
2720    }
2721
2722    /**
2723     * Returns if the current page is used to manage model groups.<p>
2724     *
2725     * @return <code>true</code> if the current page is used to manage model groups
2726     */
2727    public boolean isModelGroupPage() {
2728
2729        CmsResource page = getPageResource();
2730        return (page != null) && CmsContainerpageService.isEditingModelGroups(m_cms, page);
2731
2732    }
2733
2734    /**
2735     * Gets the link wrapper for the given path.
2736     *
2737     * @param path the path
2738     * @return the link wrapper
2739     */
2740    public CmsJspLinkWrapper link(String path) {
2741
2742        return CmsJspObjectValueWrapper.createWrapper(m_cms, path).getToLink();
2743
2744    }
2745
2746    /**
2747     * Replaces the current element with a copy to which some settings are added.
2748     *
2749     * <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.
2750     *
2751     * @param settings the settings to add
2752     */
2753    public void modifySettings(Map<String, String> settings) {
2754
2755        if (m_element != null) {
2756            if ((settings != null) && !settings.isEmpty()) {
2757                m_element = m_element.clone();
2758                if (m_element.getSettings() != null) {
2759                    m_element.getSettings().putAll(settings);
2760                } else {
2761                    LOG.error("Trying to modify null settings:" + m_element.getInstanceId());
2762                }
2763            }
2764        }
2765    }
2766
2767    /**
2768     * Gets the resource wrapper for a given path or id.
2769     *
2770     * @param str a path or structure id
2771     * @return the wrapper for the resource with the given path or id
2772     */
2773    public CmsJspResourceWrapper readResource(String str) {
2774
2775        return getVfs().getReadResource().get(str);
2776
2777    }
2778
2779    /**
2780     * Reads an XML content and returns it as a content access bean
2781     * @param str path or id
2782     * @return the content access bean for the content with the given path or id
2783     */
2784    public CmsJspContentAccessBean readXml(String str) {
2785
2786        return getVfs().getReadXml().get(str);
2787    }
2788
2789    /**
2790     * Renders the elements of container in a container page wrapper as HTML (without a surrounding element).
2791     *
2792     * @param page the page wrapper
2793     * @param name the name or name prefix of the container
2794     * @return the rendered HTML
2795     */
2796    public String renderContainer(CmsJspContainerPageWrapper page, String name) {
2797
2798        String result = page.renderContainer(this, name);
2799        return result;
2800    }
2801
2802    /**
2803     * Sets the container the currently rendered element is part of.<p>
2804     *
2805     * @param container the container the currently rendered element is part of
2806     */
2807    public void setContainer(CmsContainerBean container) {
2808
2809        m_container = container;
2810    }
2811
2812    /**
2813     * Sets the detail only page.<p>
2814     *
2815     * @param detailOnlyPage the detail only page to set
2816     */
2817    public void setDetailOnlyPage(CmsContainerPageBean detailOnlyPage) {
2818
2819        m_detailOnlyPage = detailOnlyPage;
2820        clearPageData();
2821    }
2822
2823    /**
2824     * Sets if the page is in drag mode.<p>
2825     *
2826     * @param isDragMode if the page is in drag mode
2827     */
2828    public void setDragMode(boolean isDragMode) {
2829
2830        m_isDragMode = isDragMode;
2831    }
2832
2833    /**
2834     * Sets the flag to indicate if in drag and drop mode.<p>
2835     *
2836     * @param edited <code>true</code> if in drag and drop mode
2837     */
2838    public void setEdited(boolean edited) {
2839
2840        m_edited = edited;
2841    }
2842
2843    /**
2844     * 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.
2845     *
2846     * <p>Does nothing outside of edit mode.
2847     *
2848     * @param path the site path of a style sheet
2849     * @return the meta tag
2850     */
2851    public String setEditorCssPath(String path) {
2852
2853        if (getIsEditMode()) {
2854            return "\n<meta name=\""
2855                + CmsGwtConstants.META_EDITOR_STYLESHEET
2856                + "\" content=\""
2857                + CmsEncoder.escapeXml(path)
2858                + "\">\n";
2859        } else {
2860            return "";
2861        }
2862    }
2863
2864    /**
2865     * Sets the currently rendered element.<p>
2866     *
2867     * @param element the currently rendered element to set
2868     */
2869    public void setElement(CmsContainerElementBean element) {
2870
2871        m_element = element;
2872    }
2873
2874    /**
2875     * Enables / disables the flag that forces edit mode to be disabled.
2876     *
2877     * @param forceDisableEditMode the new value for the flag
2878     */
2879    public void setForceDisableEditMode(boolean forceDisableEditMode) {
2880
2881        m_forceDisableEditMode = forceDisableEditMode;
2882    }
2883
2884    /**
2885     * Sets the currently displayed container page.<p>
2886     *
2887     * @param page the currently displayed container page to set
2888     */
2889    public void setPage(CmsContainerPageBean page) {
2890
2891        m_page = page;
2892        clearPageData();
2893    }
2894
2895    /**
2896     * Converts the given object to a resource wrapper and returns it, or returns null if the conversion fails
2897     * @param obj the object to convert
2898     * @return the resource wrapper
2899     */
2900    public CmsJspResourceWrapper toResource(Object obj) {
2901
2902        Object wrapper = wrap(obj);
2903        try {
2904            if (obj instanceof A_CmsJspValueWrapper) {
2905                return ((A_CmsJspValueWrapper)obj).getToResource();
2906            } else if (obj instanceof CmsJspResourceWrapper) {
2907                return ((CmsJspResourceWrapper)obj).getToResource();
2908            } else {
2909                // in case we add another wrapper with a getToResource method that doesn't extend A_CmsJspValueWrapper
2910                return (CmsJspResourceWrapper)wrapper.getClass().getMethod("getToResource").invoke(wrapper);
2911            }
2912        } catch (Exception e) {
2913            LOG.debug(e.getLocalizedMessage(), e);
2914            return null;
2915        }
2916    }
2917
2918    /**
2919     * Updates the internally stored OpenCms user context.<p>
2920     *
2921     * @param cms the new OpenCms user context
2922     */
2923    public void updateCmsObject(CmsObject cms) {
2924
2925        try {
2926            m_cms = OpenCms.initCmsObject(cms);
2927        } catch (CmsException e) {
2928            LOG.error(e.getLocalizedMessage(), e);
2929            m_cms = cms;
2930        }
2931        try {
2932            m_config = OpenCms.getADEManager().lookupConfigurationWithCache(cms, cms.getRequestContext().getRootUri());
2933        } catch (Exception e) {
2934            LOG.error(e.getLocalizedMessage(), e);
2935        }
2936    }
2937
2938    /**
2939     * Updates the standard context bean from the request.<p>
2940     *
2941     * @param cmsFlexRequest the request from which to update the data
2942     */
2943    public void updateRequestData(CmsFlexRequest cmsFlexRequest) {
2944
2945        CmsResource detailRes = CmsDetailPageResourceHandler.getDetailResource(cmsFlexRequest);
2946        m_detailContentResource = detailRes;
2947        m_request = cmsFlexRequest;
2948    }
2949
2950    /**
2951     * 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.
2952     *
2953     * @return the URI of the page or detail content
2954     */
2955    public String uri() {
2956
2957        return isDetailRequest() ? getDetailContent().getSitePath() : getRequestContext().getUri();
2958    }
2959
2960    /**
2961     * Returns an EL access wrapper map for the given object.<p>
2962     *
2963     * If the object is a {@link CmsResource}, then a {@link CmsJspResourceWrapper} is returned.
2964     * Otherwise the object is wrapped in a {@link CmsJspObjectValueWrapper}.<p>
2965     *
2966     * If the object is already is a wrapper, it is returned unchanged.<p>
2967     *
2968     * @param obj the object to wrap
2969     * @return an EL access wrapper map for the given object
2970     */
2971    public Object wrap(Object obj) {
2972
2973        if ((obj instanceof A_CmsJspValueWrapper) || (obj instanceof CmsJspResourceWrapper)) {
2974            return obj;
2975        } else if (obj instanceof CmsResource) {
2976            return CmsJspResourceWrapper.wrap(m_cms, (CmsResource)obj);
2977        } else {
2978            return CmsJspObjectValueWrapper.createWrapper(m_cms, obj);
2979        }
2980    }
2981
2982    /**
2983     * Accessor for the CmsObject.
2984     *
2985     * @return the CmsObject
2986     */
2987    protected CmsObject getCmsObject() {
2988
2989        return m_cms;
2990    }
2991
2992    /**
2993     * Returns the formatter configuration to the given element.<p>
2994     *
2995     * @param element the element
2996     *
2997     * @return the formatter configuration
2998     */
2999    protected I_CmsFormatterBean getElementFormatter(CmsContainerElementBean element) {
3000
3001        if (m_elementInstances == null) {
3002            initPageData();
3003        }
3004        I_CmsFormatterBean formatter = null;
3005        CmsContainerBean container = m_parentContainers.get(element.getInstanceId());
3006        if (container == null) {
3007            // use the current container
3008            container = getContainer();
3009        }
3010        if (container != null) {
3011            String containerName = container.getName();
3012            Map<String, String> settings = element.getSettings();
3013            if (settings != null) {
3014                String formatterConfigId = settings.get(CmsFormatterConfig.getSettingsKeyForContainer(containerName));
3015                I_CmsFormatterBean dynamicFmt = m_config.findFormatter(formatterConfigId);
3016                if (dynamicFmt != null) {
3017                    formatter = dynamicFmt;
3018                }
3019            }
3020            if (formatter == null) {
3021                try {
3022                    CmsResource resource = m_cms.readResource(m_cms.getRequestContext().getUri());
3023
3024                    CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
3025                        m_cms,
3026                        resource.getRootPath());
3027                    CmsFormatterConfiguration formatters = config.getFormatters(m_cms, resource);
3028                    int width = -2;
3029                    try {
3030                        width = Integer.parseInt(container.getWidth());
3031                    } catch (Exception e) {
3032                        LOG.debug(e.getLocalizedMessage(), e);
3033                    }
3034                    formatter = formatters.getDefaultSchemaFormatter(container.getType(), width);
3035                } catch (CmsException e1) {
3036                    if (LOG.isWarnEnabled()) {
3037                        LOG.warn(e1.getLocalizedMessage(), e1);
3038                    }
3039                }
3040            }
3041        }
3042        return formatter;
3043    }
3044
3045    /**
3046     * Returns the title according to the given locale.
3047     * @param locale the locale for which the title should be read.
3048     * @return the title according to the given locale
3049     */
3050    protected String getLocaleSpecificTitle(Locale locale) {
3051
3052        String result = null;
3053
3054        try {
3055
3056            if (isDetailRequest()) {
3057                // this is a request to a detail page
3058                CmsResource res = getDetailContent();
3059                CmsFile file = m_cms.readFile(res);
3060                CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, file);
3061                result = content.getHandler().getTitleMapping(m_cms, content, m_cms.getRequestContext().getLocale());
3062                if (result == null) {
3063                    // title not found, maybe no mapping OR not available in the current locale
3064                    // read the title of the detail resource as fall back (may contain mapping from another locale)
3065                    result = m_cms.readPropertyObject(
3066                        res,
3067                        CmsPropertyDefinition.PROPERTY_TITLE,
3068                        false,
3069                        locale).getValue();
3070                }
3071            }
3072            if (result == null) {
3073                // read the title of the requested resource as fall back
3074                result = m_cms.readPropertyObject(
3075                    m_cms.getRequestContext().getUri(),
3076                    CmsPropertyDefinition.PROPERTY_TITLE,
3077                    true,
3078                    locale).getValue();
3079            }
3080        } catch (CmsException e) {
3081            LOG.debug(e.getLocalizedMessage(), e);
3082        }
3083        if (CmsStringUtil.isEmptyOrWhitespaceOnly(result)) {
3084            result = "";
3085        }
3086
3087        return result;
3088    }
3089
3090    /**
3091     * Returns the parent element if available.<p>
3092     *
3093     * @param element the element
3094     *
3095     * @return the parent element or null
3096     */
3097    protected CmsContainerElementBean getParentElement(CmsContainerElementBean element) {
3098
3099        if (m_elementInstances == null) {
3100            initPageData();
3101        }
3102        CmsContainerElementBean parent = null;
3103        CmsContainerBean cont = m_parentContainers.get(element.getInstanceId());
3104        if ((cont != null) && cont.isNestedContainer()) {
3105            parent = m_elementInstances.get(cont.getParentInstanceId());
3106        }
3107        return parent;
3108    }
3109
3110    /**
3111     * Accessor for the sitemap configuration.
3112     *
3113     * @return the sitemap configuration
3114     */
3115    protected CmsADEConfigData getSitemapConfigInternal() {
3116
3117        return m_config;
3118    }
3119
3120    /**
3121     * Reads a dynamic function bean, given its name in the module configuration.<p>
3122     *
3123     * @param configuredName the name of the dynamic function in the module configuration
3124     * @return the dynamic function bean for the dynamic function configured under that name
3125     *
3126     * @throws CmsException if something goes wrong
3127     */
3128    protected CmsDynamicFunctionBean readDynamicFunctionBean(String configuredName) throws CmsException {
3129
3130        CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
3131            m_cms,
3132            m_cms.addSiteRoot(m_cms.getRequestContext().getUri()));
3133        CmsFunctionReference functionRef = config.getFunctionReference(configuredName);
3134        if (functionRef == null) {
3135            return null;
3136        }
3137        CmsDynamicFunctionParser parser = new CmsDynamicFunctionParser();
3138        CmsResource functionResource = m_cms.readResource(functionRef.getStructureId());
3139        CmsDynamicFunctionBean result = parser.parseFunctionBean(m_cms, functionResource);
3140        return result;
3141    }
3142
3143    /**
3144     * Wraps a list of formatter beans for use in JSPs.
3145     *
3146     * @param formatters the formatters to wrap
3147     * @return the wrapped formatters
3148     */
3149    protected List<CmsFormatterInfoWrapper> wrapFormatters(Collection<? extends I_CmsFormatterBean> formatters) {
3150
3151        List<I_CmsFormatterBean> formattersToSort = new ArrayList<>(formatters);
3152        List<CmsResourceTypeConfig> types = m_config.getResourceTypes();
3153
3154        // we want to 'group' the returned formatters by resource type, which is slightly
3155        // complicated by the fact that formatters can support multiple resource types.
3156
3157        // first build a map that records the positions of the resource types configured in the sitemap configuration
3158        Map<String, Integer> typePositionsByTypeName = new HashMap<>();
3159        for (int i = 0; i < types.size(); i++) {
3160            CmsResourceTypeConfig singleType = types.get(i);
3161            typePositionsByTypeName.put(singleType.getTypeName(), Integer.valueOf(i));
3162        }
3163        // for each formatter, save the lowest position of any resource type it supports
3164        Map<String, Integer> lowestResourceTypePositionsByFormatterId = new HashMap<>();
3165        for (I_CmsFormatterBean formatter : formatters) {
3166            int pos = Integer.MAX_VALUE;
3167            for (String typeName : formatter.getResourceTypeNames()) {
3168                Integer typeOrder = typePositionsByTypeName.get(typeName);
3169                if (typeOrder != null) {
3170                    pos = Math.min(pos, typeOrder.intValue());
3171                }
3172            }
3173            lowestResourceTypePositionsByFormatterId.put(formatter.getId(), Integer.valueOf(pos));
3174        }
3175
3176        // now we can group the formatters by types using sorting, and inside the groups we sort by formatter rank
3177        Collections.sort(formattersToSort, new Comparator<I_CmsFormatterBean>() {
3178
3179            public int compare(I_CmsFormatterBean o1, I_CmsFormatterBean o2) {
3180
3181                return ComparisonChain.start().compare(getTypePosition(o1), getTypePosition(o2)).compare(
3182                    o2.getRank(),
3183                    o1.getRank()).result();
3184            }
3185
3186            public int getTypePosition(I_CmsFormatterBean formatter) {
3187
3188                return lowestResourceTypePositionsByFormatterId.computeIfAbsent(
3189                    formatter.getId(),
3190                    id -> Integer.valueOf(Integer.MAX_VALUE));
3191            }
3192
3193        });
3194        return formattersToSort.stream().map(
3195            formatter -> new CmsFormatterInfoWrapper(m_cms, m_config, formatter)).collect(Collectors.toList());
3196
3197    }
3198
3199    /**
3200     * Adds the mappings of the given formatter configuration.<p>
3201     *
3202     * @param formatterBean the formatter bean
3203     * @param elementId the element content structure id
3204     * @param resolver The macro resolver used on key and default value of the mappings
3205     * @param isDetailContent in case of a detail content
3206     */
3207    private void addMappingsForFormatter(
3208        I_CmsFormatterBean formatterBean,
3209        CmsUUID elementId,
3210        CmsMacroResolver resolver,
3211        boolean isDetailContent) {
3212
3213        if ((formatterBean != null) && (formatterBean.getMetaMappings() != null)) {
3214            for (CmsMetaMapping map : formatterBean.getMetaMappings()) {
3215                String key = map.getKey();
3216                key = resolver.resolveMacros(key);
3217                // the detail content mapping overrides other mappings
3218                if (isDetailContent
3219                    || !m_metaMappings.containsKey(key)
3220                    || (m_metaMappings.get(key).m_order <= map.getOrder())) {
3221                    MetaMapping mapping = new MetaMapping();
3222                    mapping.m_key = key;
3223                    mapping.m_elementXPath = map.getElement();
3224                    mapping.m_defaultValue = resolver.resolveMacros(map.getDefaultValue());
3225                    mapping.m_order = map.getOrder();
3226                    mapping.m_contentId = elementId;
3227                    m_metaMappings.put(key, mapping);
3228                }
3229            }
3230        }
3231    }
3232
3233    /**
3234     * Clears the page element data.<p>
3235     */
3236    private void clearPageData() {
3237
3238        m_elementInstances = null;
3239        m_parentContainers = null;
3240    }
3241
3242    /**
3243     * Finds the first ancestor of a resource matching a given predicate.
3244     *
3245     * @param cms the CMS context
3246     * @param resource the resource
3247     * @param predicate the predicate to test
3248     *
3249     * @return the first ancestor matching the predicate (which may possibly be the given resource itself), or null if no matching ancestor is found
3250     * @throws CmsException
3251     */
3252    private CmsResource findAncestor(CmsObject cms, CmsResource resource, Predicate<CmsResource> predicate) {
3253
3254        try {
3255            CmsObject rootCms = OpenCms.initCmsObject(cms);
3256            rootCms.getRequestContext().setSiteRoot("");
3257            CmsResource ancestor = resource;
3258            while (ancestor != null) {
3259                if (predicate.test(ancestor)) {
3260                    return ancestor;
3261                }
3262                String parentFolder = CmsResource.getParentFolder(ancestor.getRootPath());
3263                if (parentFolder == null) {
3264                    break;
3265                }
3266                try {
3267                    ancestor = rootCms.readResource(parentFolder, CmsResourceFilter.IGNORE_EXPIRATION);
3268                } catch (CmsException e) {
3269                    LOG.info(e.getLocalizedMessage(), e);
3270                    break;
3271                }
3272            }
3273        } catch (CmsException e) {
3274            LOG.error(e.getLocalizedMessage(), e);
3275        }
3276        return null;
3277    }
3278
3279    /**
3280     * Generates a link to the bundle editor to edit the provided message key.
3281     * The back link for the editor is the current uri with the provided backLinkAnchor added as anchor.
3282     *
3283     * If the bundle resource for the key could not be found, <code>null</code> is returned.
3284     *
3285     * @param messageKey the message key to open the bundle editor for.
3286     * @param backLinkAnchor the anchor id to add to the backlink to the page. If <code>null</code> no anchor is added to the backlink.
3287     * @param backLinkParams request parameters to add to the backlink without leading '?', e.g. "param1=a&param2=b".
3288     * @param bundleName the name of the bundle to search the key in. If <code>null</code> the bundle is detected automatically.
3289     * @param nameFilters if more than one bundle is matched, bundles that match (substring matching) at least one of the provided strings are preferred.
3290     *  This option is only useful, if the bundleName is not provided.
3291     *
3292     * @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.
3293     */
3294    private String getBundleEditorLink(
3295        String messageKey,
3296        String backLinkAnchor,
3297        String backLinkParams,
3298        String bundleName,
3299        List<String> nameFilters) {
3300
3301        if (!m_cms.getRequestContext().getCurrentProject().isOnlineProject()) {
3302            String filePath = null;
3303            if (null == bundleName) {
3304                filePath = getBundleRootPath(messageKey, nameFilters);
3305            } else {
3306                ResourceBundle bundle = CmsResourceBundleLoader.getBundle(
3307                    bundleName,
3308                    m_cms.getRequestContext().getLocale());
3309                if (bundle instanceof CmsVfsResourceBundle) {
3310                    CmsVfsResourceBundle vfsBundle = (CmsVfsResourceBundle)bundle;
3311                    filePath = vfsBundle.getParameters().getBasePath();
3312                }
3313            }
3314            try {
3315                if (null == filePath) {
3316                    throw new Exception("Could not determine the VFS root path of the bundle.");
3317                }
3318                CmsUUID structureId = m_cms.readResource(
3319                    m_cms.getRequestContext().removeSiteRoot(filePath)).getStructureId();
3320                String backLink = OpenCms.getLinkManager().getServerLink(m_cms, m_cms.getRequestContext().getUri());
3321                if (!((null == backLinkParams) || backLinkParams.isEmpty())) {
3322                    backLink = backLink + "?" + backLinkParams;
3323                }
3324                if (!((null == backLinkAnchor) || backLinkAnchor.isEmpty())) {
3325                    backLink = backLink + "#" + backLinkAnchor;
3326                }
3327                String appState = CmsEditor.getEditState(structureId, false, backLink);
3328                if (null != messageKey) {
3329                    appState = A_CmsWorkplaceApp.addParamToState(
3330                        appState,
3331                        CmsMessageBundleEditor.PARAM_KEYFILTER,
3332                        messageKey);
3333                }
3334                String link = CmsVaadinUtils.getWorkplaceLink(CmsEditorConfiguration.APP_ID, appState);
3335                return link;
3336            } catch (Throwable t) {
3337                if (LOG.isWarnEnabled()) {
3338                    String message = "Failed to open bundle editor for key '"
3339                        + messageKey
3340                        + "' and bundle with name '"
3341                        + bundleName
3342                        + "'.";
3343                    if (LOG.isDebugEnabled()) {
3344                        LOG.debug(message, t);
3345                    } else {
3346                        LOG.warn(message);
3347                    }
3348                }
3349            }
3350        }
3351        return null;
3352    }
3353
3354    /**
3355     * Returns the container page bean for the give resource.<p>
3356     *
3357     * @param config the sitemap config to use
3358     * @param pageResource the resource
3359     *
3360     * @return the container page bean
3361     *
3362     * @throws CmsException in case reading the page bean fails
3363     */
3364    private CmsContainerPageBean getPage(CmsADEConfigData config, CmsResource pageResource) throws CmsException {
3365
3366        CmsContainerPageBean result = null;
3367        if ((pageResource != null) && CmsResourceTypeXmlContainerPage.isContainerPage(pageResource)) {
3368            CmsXmlContainerPage xmlContainerPage = CmsXmlContainerPageFactory.unmarshal(m_cms, pageResource, m_request);
3369            result = xmlContainerPage.getContainerPage(m_cms);
3370            CmsModelGroupHelper modelHelper = new CmsModelGroupHelper(
3371                m_cms,
3372                config,
3373                CmsJspTagEditable.isEditableRequest(m_request) && (m_request instanceof HttpServletRequest)
3374                ? CmsADESessionCache.getCache((HttpServletRequest)m_request, m_cms)
3375                : null,
3376                CmsContainerpageService.isEditingModelGroups(m_cms, pageResource));
3377            result = modelHelper.readModelGroups(xmlContainerPage.getContainerPage(m_cms));
3378        }
3379        return result;
3380    }
3381
3382    /**
3383     * Convenience method for getting a request attribute without an explicit cast.<p>
3384     *
3385     * @param name the attribute name
3386     * @return the request attribute
3387     */
3388    @SuppressWarnings("unchecked")
3389    private <A> A getRequestAttribute(String name) {
3390
3391        Object attribute = m_request.getAttribute(name);
3392
3393        return attribute != null ? (A)attribute : null;
3394    }
3395
3396    /**
3397     * Initializes the mapping configuration.<p>
3398     */
3399    private void initMetaMappings() {
3400
3401        if (m_metaMappings == null) {
3402            m_metaMappings = new HashMap<String, MetaMapping>();
3403            try {
3404                initPage();
3405                CmsMacroResolver resolver = new CmsMacroResolver();
3406                resolver.setKeepEmptyMacros(true);
3407                resolver.setCmsObject(m_cms);
3408                resolver.setMessages(OpenCms.getWorkplaceManager().getMessages(getLocale()));
3409                CmsResourceFilter filter = getIsEditMode()
3410                ? CmsResourceFilter.IGNORE_EXPIRATION
3411                : CmsResourceFilter.DEFAULT;
3412                if (m_page != null) {
3413                    for (CmsContainerBean container : m_page.getContainers().values()) {
3414                        for (CmsContainerElementBean element : container.getElements()) {
3415                            String settingsKey = CmsFormatterConfig.getSettingsKeyForContainer(container.getName());
3416                            String formatterConfigId = element.getSettings() != null
3417                            ? element.getSettings().get(settingsKey)
3418                            : null;
3419                            I_CmsFormatterBean formatterBean = null;
3420                            formatterBean = m_config.findFormatter(formatterConfigId);
3421                            if ((formatterBean != null)
3422                                && formatterBean.useMetaMappingsForNormalElements()
3423                                && m_cms.existsResource(element.getId(), filter)) {
3424                                addMappingsForFormatter(formatterBean, element.getId(), resolver, false);
3425                            }
3426
3427                        }
3428                    }
3429                }
3430                if (getDetailContentId() != null) {
3431                    try {
3432                        CmsResource detailContent = m_cms.readResource(
3433                            getDetailContentId(),
3434                            CmsResourceFilter.ignoreExpirationOffline(m_cms));
3435                        CmsFormatterConfiguration config = OpenCms.getADEManager().lookupConfiguration(
3436                            m_cms,
3437                            m_cms.getRequestContext().getRootUri()).getFormatters(m_cms, detailContent);
3438                        for (I_CmsFormatterBean formatter : config.getDetailFormatters()) {
3439                            addMappingsForFormatter(formatter, getDetailContentId(), resolver, true);
3440                        }
3441                    } catch (CmsException e) {
3442                        LOG.error(
3443                            Messages.get().getBundle().key(
3444                                Messages.ERR_READING_REQUIRED_RESOURCE_1,
3445                                getDetailContentId()),
3446                            e);
3447                    }
3448                }
3449            } catch (Exception e) {
3450                LOG.error(e.getLocalizedMessage(), e);
3451            }
3452        }
3453    }
3454
3455    /**
3456     * Initializes the page element data.<p>
3457     */
3458    private void initPageData() {
3459
3460        m_elementInstances = new HashMap<String, CmsContainerElementBean>();
3461        m_parentContainers = new HashMap<String, CmsContainerBean>();
3462        if (m_page != null) {
3463            for (CmsContainerBean container : m_page.getContainers().values()) {
3464                for (CmsContainerElementBean element : container.getElements()) {
3465                    m_elementInstances.put(element.getInstanceId(), element);
3466                    m_parentContainers.put(element.getInstanceId(), container);
3467                    try {
3468                        if (element.isGroupContainer(m_cms) || element.isInheritedContainer(m_cms)) {
3469                            List<CmsContainerElementBean> children;
3470                            if (element.isGroupContainer(m_cms)) {
3471                                children = CmsJspTagContainer.getGroupContainerElements(
3472                                    m_cms,
3473                                    element,
3474                                    m_request,
3475                                    container.getType());
3476                            } else {
3477                                children = CmsJspTagContainer.getInheritedContainerElements(m_cms, element);
3478                            }
3479                            for (CmsContainerElementBean childElement : children) {
3480                                m_elementInstances.put(childElement.getInstanceId(), childElement);
3481                                m_parentContainers.put(childElement.getInstanceId(), container);
3482                            }
3483                        }
3484                    } catch (CmsException e) {
3485                        LOG.error(e.getLocalizedMessage(), e);
3486                    }
3487                }
3488            }
3489            // also add detail only data
3490            if (m_detailOnlyPage != null) {
3491                for (CmsContainerBean container : m_detailOnlyPage.getContainers().values()) {
3492                    for (CmsContainerElementBean element : container.getElements()) {
3493                        m_elementInstances.put(element.getInstanceId(), element);
3494                        m_parentContainers.put(element.getInstanceId(), container);
3495                    }
3496                }
3497            }
3498        }
3499    }
3500
3501}