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