001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (https://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ade.configuration;
029
030import org.opencms.ade.configuration.CmsADEConfigData.DetailInfo;
031import org.opencms.ade.configuration.CmsElementView.ElementViewComparator;
032import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCache;
033import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCacheState;
034import org.opencms.ade.configuration.plugins.CmsTemplatePlugin;
035import org.opencms.ade.configuration.plugins.CmsTemplatePluginFinder;
036import org.opencms.ade.containerpage.inherited.CmsContainerConfigurationCache;
037import org.opencms.ade.containerpage.inherited.CmsContainerConfigurationWriter;
038import org.opencms.ade.containerpage.inherited.CmsInheritedContainerState;
039import org.opencms.ade.detailpage.CmsDetailPageConfigurationWriter;
040import org.opencms.ade.detailpage.CmsDetailPageInfo;
041import org.opencms.ade.detailpage.I_CmsDetailPageHandler;
042import org.opencms.ade.upload.CmsUploadWarningTable;
043import org.opencms.configuration.CmsSystemConfiguration;
044import org.opencms.db.I_CmsProjectDriver;
045import org.opencms.file.CmsFile;
046import org.opencms.file.CmsObject;
047import org.opencms.file.CmsProject;
048import org.opencms.file.CmsRequestContext;
049import org.opencms.file.CmsResource;
050import org.opencms.file.CmsResourceFilter;
051import org.opencms.file.CmsUser;
052import org.opencms.file.types.CmsResourceTypeFunctionConfig;
053import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
054import org.opencms.file.types.CmsResourceTypeXmlContent;
055import org.opencms.file.types.I_CmsResourceType;
056import org.opencms.gwt.shared.CmsGwtConstants;
057import org.opencms.gwt.shared.CmsPermissionInfo;
058import org.opencms.gwt.shared.CmsTemplateContextInfo;
059import org.opencms.i18n.CmsEncoder;
060import org.opencms.i18n.CmsLocaleManager;
061import org.opencms.json.JSONArray;
062import org.opencms.json.JSONException;
063import org.opencms.json.JSONObject;
064import org.opencms.jsp.CmsJspNavBuilder;
065import org.opencms.jsp.CmsJspNavElement;
066import org.opencms.jsp.CmsJspTagLink;
067import org.opencms.jsp.util.CmsJspStandardContextBean;
068import org.opencms.jsp.util.CmsTemplatePluginWrapper;
069import org.opencms.loader.CmsLoaderException;
070import org.opencms.main.CmsException;
071import org.opencms.main.CmsLog;
072import org.opencms.main.OpenCms;
073import org.opencms.main.OpenCmsServlet;
074import org.opencms.monitor.CmsMemoryMonitor;
075import org.opencms.relations.CmsRelation;
076import org.opencms.relations.CmsRelationFilter;
077import org.opencms.security.CmsPermissionSet;
078import org.opencms.security.CmsRole;
079import org.opencms.util.CmsRequestUtil;
080import org.opencms.util.CmsStringUtil;
081import org.opencms.util.CmsUUID;
082import org.opencms.util.CmsWaitHandle;
083import org.opencms.workplace.explorer.CmsExplorerTypeSettings;
084import org.opencms.workplace.explorer.CmsResourceUtil;
085import org.opencms.xml.CmsXmlContentDefinition;
086import org.opencms.xml.CmsXmlException;
087import org.opencms.xml.containerpage.CmsADECache;
088import org.opencms.xml.containerpage.CmsADECacheSettings;
089import org.opencms.xml.containerpage.CmsContainerElementBean;
090import org.opencms.xml.containerpage.I_CmsFormatterBean;
091import org.opencms.xml.containerpage.Messages;
092import org.opencms.xml.content.CmsXmlContent;
093import org.opencms.xml.content.CmsXmlContentFactory;
094import org.opencms.xml.content.CmsXmlContentProperty;
095import org.opencms.xml.content.CmsXmlContentProperty.Visibility;
096import org.opencms.xml.content.CmsXmlContentPropertyHelper;
097import org.opencms.xml.content.I_CmsXmlContentHandler;
098import org.opencms.xml.types.I_CmsXmlContentValue;
099
100import java.util.ArrayList;
101import java.util.Collections;
102import java.util.Comparator;
103import java.util.HashMap;
104import java.util.HashSet;
105import java.util.Iterator;
106import java.util.LinkedHashMap;
107import java.util.List;
108import java.util.Locale;
109import java.util.Map;
110import java.util.Map.Entry;
111import java.util.Set;
112import java.util.function.Predicate;
113import java.util.stream.Collectors;
114import java.util.stream.Stream;
115
116import javax.servlet.ServletRequest;
117import javax.servlet.http.HttpServletRequest;
118import javax.servlet.http.HttpServletResponse;
119
120import org.apache.commons.logging.Log;
121
122import com.google.common.collect.Lists;
123import com.google.common.collect.Maps;
124import com.google.common.collect.Multimap;
125
126/**
127 * This is the main class used to access the ADE configuration and also accomplish some other related tasks
128 * like loading/saving favorite and recent lists.<p>
129 */
130public class CmsADEManager {
131
132    /** JSON property name constant. */
133    protected enum FavListProp {
134        /** element property. */
135        ELEMENT,
136        /** formatter property. */
137        FORMATTER,
138        /** properties property. */
139        PROPERTIES;
140    }
141
142    /**
143     * A status enum for the initialization status.<p>
144     */
145    protected enum Status {
146        /** already initialized. */
147        initialized,
148        /** currently initializing. */
149        initializing,
150        /** not initialized. */
151        notInitialized
152    }
153
154    /** The client id separator. */
155    public static final String CLIENT_ID_SEPERATOR = "#";
156
157    /** The configuration file name. */
158    public static final String CONFIG_FILE_NAME = ".config";
159
160    /** The name of the sitemap configuration file type. */
161    public static final String CONFIG_FOLDER_TYPE = "content_folder";
162
163    /** The path for sitemap configuration files relative from the base path. */
164    public static final String CONFIG_SUFFIX = "/"
165        + CmsADEManager.CONTENT_FOLDER_NAME
166        + "/"
167        + CmsADEManager.CONFIG_FILE_NAME;
168
169    /** The name of the sitemap configuration file type. */
170    public static final String CONFIG_TYPE = "sitemap_config";
171
172    /** The content folder name. */
173    public static final String CONTENT_FOLDER_NAME = ".content";
174
175    /** The default detail page type name. */
176    public static final String DEFAULT_DETAILPAGE_TYPE = CmsGwtConstants.DEFAULT_DETAILPAGE_TYPE;
177
178    /** Default favorite/recent list size constant. */
179    public static final int DEFAULT_ELEMENT_LIST_SIZE = 10;
180
181    /** The name of the element view configuration file type. */
182    public static final String ELEMENT_VIEW_TYPE = "elementview";
183
184    /** The name of the module configuration file type. */
185    public static final String MODULE_CONFIG_TYPE = "module_config";
186
187    /** The aADE configuration module name. */
188    public static final String MODULE_NAME_ADE_CONFIG = "org.opencms.base";
189
190    /** Node name for the nav level link value. */
191    public static final String N_LINK = "Link";
192
193    /** Node name for the nav level type value. */
194    public static final String N_TYPE = "Type";
195
196    /** The path to the sitemap editor JSP. */
197    public static final String PATH_SITEMAP_EDITOR_JSP = "/system/workplace/commons/sitemap.jsp";
198
199    /** User additional info key constant. */
200    protected static final String ADDINFO_ADE_FAVORITE_LIST = "ADE_FAVORITE_LIST";
201
202    /** User additional info key constant. */
203    protected static final String ADDINFO_ADE_RECENT_LIST = "ADE_RECENT_LIST";
204
205    /** User additional info key constant. */
206    protected static final String ADDINFO_ADE_SHOW_EDITOR_HELP = "ADE_SHOW_EDITOR_HELP";
207
208    /** The logger instance for this class. */
209    private static final Log LOG = CmsLog.getLog(CmsADEManager.class);
210
211    /** The cache instance. */
212    private CmsADECache m_cache;
213
214    /** The sitemap configuration file type. */
215    private I_CmsResourceType m_configType;
216
217    /** The detail page handler. */
218    private I_CmsDetailPageHandler m_detailPageHandler;
219
220    /** The element view configuration file type. */
221    private I_CmsResourceType m_elementViewType;
222
223    /** The initialization status. */
224    private Status m_initStatus = Status.notInitialized;
225
226    /** The module configuration file type. */
227    private I_CmsResourceType m_moduleConfigType;
228
229    /** The online cache instance. */
230    private CmsConfigurationCache m_offlineCache;
231
232    /** The offline CMS context. */
233    private CmsObject m_offlineCms;
234
235    /** The offline inherited container configuration cache. */
236    private CmsContainerConfigurationCache m_offlineContainerConfigurationCache;
237
238    /** The detail id cache for the Offline project. */
239    private CmsDetailNameCache m_offlineDetailIdCache;
240
241    /** The offline formatter bean cache. */
242    private CmsFormatterConfigurationCache m_offlineFormatterCache;
243
244    /** The offline cache instance. */
245    private CmsConfigurationCache m_onlineCache;
246
247    /** The online CMS context. */
248    private CmsObject m_onlineCms;
249
250    /** The online inherited container configuration cache. */
251    private CmsContainerConfigurationCache m_onlineContainerConfigurationCache;
252
253    /** The Online project detail id cache. */
254    private CmsDetailNameCache m_onlineDetailIdCache;
255
256    /** The online formatter bean cache. */
257    private CmsFormatterConfigurationCache m_onlineFormatterCache;
258
259    /** ADE parameters. */
260    private Map<String, String> m_parameters;
261
262    /** The table of upload warnings. */
263    private CmsUploadWarningTable m_uploadWarningTable = new CmsUploadWarningTable();
264
265    /** The providers used to inject additional configuration details into the sitemap configuration. */
266    private List<I_CmsSitemapExtraInfoProvider> m_sitemapExtraInfoProviders = new ArrayList<>();
267
268    /**
269     * Creates a new ADE manager.<p>
270     *
271     * @param adminCms a CMS context with admin privileges
272     * @param memoryMonitor the memory monitor instance
273     * @param systemConfiguration the system configuration
274     */
275    public CmsADEManager(
276        CmsObject adminCms,
277        CmsMemoryMonitor memoryMonitor,
278        CmsSystemConfiguration systemConfiguration) {
279
280        // initialize the ade cache
281        CmsADECacheSettings cacheSettings = systemConfiguration.getAdeCacheSettings();
282        if (cacheSettings == null) {
283            cacheSettings = new CmsADECacheSettings();
284        }
285        m_onlineCms = adminCms;
286        m_cache = new CmsADECache(memoryMonitor, cacheSettings);
287        m_parameters = new LinkedHashMap<String, String>(systemConfiguration.getAdeParameters());
288        m_detailPageHandler = systemConfiguration.getDetailPageHandler();
289        // further initialization is done by the initialize() method. We don't do that in the constructor,
290        // because during the setup the configuration resource types don't exist yet.
291    }
292
293    /**
294     * Adds a wait handle for the next cache update to a formatter configuration.<p>
295     *
296     * @param online true if we want to add a wait handle to the online cache, else the offline cache
297     * @return the wait handle that has been added
298     */
299    public CmsWaitHandle addFormatterCacheWaitHandle(boolean online) {
300
301        CmsWaitHandle handle = new CmsWaitHandle(true); // single use wait handle
302        CmsFormatterConfigurationCache cache = online ? m_onlineFormatterCache : m_offlineFormatterCache;
303        cache.addWaitHandle(handle);
304        return handle;
305    }
306
307    /**
308     * Adds a sitemap extra info provider.
309     *
310     * @param provider the provider to add
311     */
312    public void addSitemapExtraInfoProvider(I_CmsSitemapExtraInfoProvider provider) {
313
314        m_sitemapExtraInfoProviders.add(provider);
315    }
316
317    /**
318     * Checks if the sitemap config can be edited by the user in the given CMS context.
319     *
320     * <p>Note: Even if this returns true, there may be other reasons preventing the sitemap configuration from being edited by the user.
321     *
322     * @param cms the CMS context to check
323     * @return false if the user should not be able to edit the sitemap configuration
324     */
325    public boolean canEditSitemapConfiguration(CmsObject cms) {
326
327        CmsRole role = getRoleForSitemapConfigEditing();
328        if (role == null) {
329            return true;
330        }
331        return OpenCms.getRoleManager().hasRole(cms, role);
332    }
333
334    /**
335     * Finds the entry point to a sitemap.<p>
336     *
337     * @param cms the CMS context
338     * @param openPath the resource path to find the sitemap to
339     *
340     * @return the sitemap entry point
341     */
342    public String findEntryPoint(CmsObject cms, String openPath) {
343
344        CmsADEConfigData configData = lookupConfiguration(cms, openPath);
345        String result = configData.getBasePath();
346        if (result == null) {
347            return cms.getRequestContext().addSiteRoot("/");
348        }
349        return result;
350    }
351
352    /**
353     * Flushes inheritance group changes so the cache is updated.<p>
354     *
355     * This is useful for test cases.
356     */
357    public void flushInheritanceGroupChanges() {
358
359        m_offlineContainerConfigurationCache.flushUpdates();
360        m_onlineContainerConfigurationCache.flushUpdates();
361    }
362
363    /**
364     * Gets the complete list of beans for the currently configured detail pages.<p>
365     *
366     * @param cms the CMS context to use
367     *
368     * @return the list of detail page infos
369     */
370    public List<CmsDetailPageInfo> getAllDetailPages(CmsObject cms) {
371
372        return getCacheState(isOnline(cms)).getAllDetailPages();
373    }
374
375    /**
376     * Gets the containerpage cache instance.<p>
377     *
378     * @return the containerpage cache instance
379     */
380    public CmsADECache getCache() {
381
382        return m_cache;
383    }
384
385    /**
386     * Gets the cached formatter beans.<p>
387     *
388     * @param online true if the Online project formatters should be returned, false for the Offline formatters
389     *
390     * @return the formatter configuration cache state
391     */
392    public CmsFormatterConfigurationCacheState getCachedFormatters(boolean online) {
393
394        CmsFormatterConfigurationCache cache = online ? m_onlineFormatterCache : m_offlineFormatterCache;
395        return cache.getState();
396    }
397
398    /**
399     * Gets the current ADE configuration cache state.<p>
400     *
401     * @param online true if you want the online state, false for the offline state
402     *
403     * @return the configuration cache state
404     */
405    public CmsADEConfigCacheState getCacheState(boolean online) {
406
407        return (online ? m_onlineCache : m_offlineCache).getState();
408    }
409
410    /**
411     * Gets the configuration file type.<p>
412     *
413     * @return the configuration file type
414     */
415    public I_CmsResourceType getConfigurationType() {
416
417        return m_configType;
418    }
419
420    /**
421     * Returns the names of the bundles configured as workplace bundles in any module configuration.
422     * @return the names of the bundles configured as workplace bundles in any module configuration.
423     */
424    public Set<String> getConfiguredWorkplaceBundles() {
425
426        CmsADEConfigData configData = internalLookupConfiguration(null, null);
427        return configData.getConfiguredWorkplaceBundles();
428    }
429
430    /**
431     * Gets the content types configured in any sitemap configuations.
432     *
433     * @param online true if the types for the Online project should be fetched
434     * @return the set of content types
435     */
436    public Set<String> getContentTypeNames(boolean online) {
437
438        CmsConfigurationCache cache = online ? m_onlineCache : m_offlineCache;
439        return cache.getState().getContentTypes();
440
441    }
442
443    /**
444     * Reads the current element bean from the request.<p>
445     *
446     * @param req the servlet request
447     *
448     * @return the element bean
449     *
450     * @throws CmsException if no current element is set
451     */
452    public CmsContainerElementBean getCurrentElement(ServletRequest req) throws CmsException {
453
454        CmsJspStandardContextBean sCBean = CmsJspStandardContextBean.getInstance(req);
455        CmsContainerElementBean element = sCBean.getElement();
456        if (element == null) {
457            throw new CmsException(
458                Messages.get().container(
459                    Messages.ERR_READING_ELEMENT_FROM_REQUEST_1,
460                    sCBean.getRequestContext().getUri()));
461        }
462        return element;
463    }
464
465    /**
466     * Gets the detail id cache for the Online or Offline projects.<p>
467     *
468     * @param online if true, gets the Online project detail id
469     *
470     * @return the detail name cache
471     */
472    public CmsDetailNameCache getDetailIdCache(boolean online) {
473
474        return online ? m_onlineDetailIdCache : m_offlineDetailIdCache;
475    }
476
477    /**
478     * Gets the detail page information for  everything.<p>
479     *
480     * @param cms the current CMS context
481     *
482     * @return the list with all the detail page information
483     */
484    public List<DetailInfo> getDetailInfo(CmsObject cms) {
485
486        return getCacheState(isOnline(cms)).getDetailInfosForSubsites(cms);
487    }
488
489    /**
490     * Gets the detail page for a content element.<p>
491     *
492     * @param cms the CMS context
493     * @param pageRootPath the element's root path
494     * @param originPath the path in which the the detail page is being requested
495     *
496     * @return the detail page for the content element
497     */
498    public String getDetailPage(CmsObject cms, String pageRootPath, String originPath) {
499
500        return getDetailPage(cms, pageRootPath, originPath, null);
501    }
502
503    /**
504     * Gets the detail page for a content element.<p>
505     *
506     * @param cms the CMS context
507     * @param rootPath the element's root path
508     * @param linkSource the path in which the the detail page is being requested
509     * @param targetDetailPage the target detail page to use
510     *
511     * @return the detail page for the content element
512     */
513    public String getDetailPage(CmsObject cms, String rootPath, String linkSource, String targetDetailPage) {
514
515        return getDetailPageHandler().getDetailPage(cms, rootPath, linkSource, targetDetailPage);
516    }
517
518    /**
519     * Gets the detail page finder.<p>
520     *
521     * @return the detail page finder
522     */
523    public I_CmsDetailPageHandler getDetailPageHandler() {
524
525        return m_detailPageHandler;
526    }
527
528    /**
529     * Returns the main detail pages for a type in all of the VFS tree.<p>
530     *
531     * @param cms the current CMS context
532     * @param type the resource type name
533     * @return a list of detail page root paths
534     */
535    public List<String> getDetailPages(CmsObject cms, String type) {
536
537        CmsConfigurationCache cache = isOnline(cms) ? m_onlineCache : m_offlineCache;
538        return cache.getState().getDetailPages(type);
539    }
540
541    /**
542     * Gets the set of types for which detail pages are defined.<p>
543     *
544     * @param cms the current CMS context
545     *
546     * @return the set of types for which detail pages are defined
547     */
548    public Set<String> getDetailPageTypes(CmsObject cms) {
549
550        return getCacheState(isOnline(cms)).getDetailPageTypes();
551    }
552
553    /**
554     * Returns the element settings for a given resource.<p>
555     *
556     * @param cms the current cms context
557     * @param resource the resource
558     *
559     * @return the element settings for a given resource
560     *
561     * @throws CmsException if something goes wrong
562     */
563    public Map<String, CmsXmlContentProperty> getElementSettings(CmsObject cms, CmsResource resource)
564    throws CmsException {
565
566        if (CmsResourceTypeXmlContent.isXmlContent(resource)) {
567            Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>();
568            Map<String, CmsXmlContentProperty> settings = CmsXmlContentDefinition.getContentHandlerForResource(
569                cms,
570                resource).getSettings(cms, resource);
571            result.putAll(settings);
572            return CmsXmlContentPropertyHelper.copyPropertyConfiguration(result);
573        }
574        return Collections.<String, CmsXmlContentProperty> emptyMap();
575    }
576
577    /**
578     * Returns the available element views.<p>
579     *
580     * @param cms the cms context
581     *
582     * @return the element views
583     */
584    public Map<CmsUUID, CmsElementView> getElementViews(CmsObject cms) {
585
586        CmsConfigurationCache cache = getCache(isOnline(cms));
587        List<CmsElementView> viewList = Lists.newArrayList();
588        viewList.addAll(cache.getState().getElementViews().values());
589        viewList.addAll(OpenCms.getWorkplaceManager().getExplorerTypeViews().values());
590        Collections.sort(viewList, new ElementViewComparator());
591        Map<CmsUUID, CmsElementView> result = Maps.newLinkedHashMap();
592        for (CmsElementView viewValue : viewList) {
593            result.put(viewValue.getId(), viewValue);
594        }
595        return result;
596    }
597
598    /**
599     * Gets the element view configuration resource type.<p>
600     *
601     * @return the element view configuration resource type
602     */
603    public I_CmsResourceType getElementViewType() {
604
605        return m_elementViewType;
606    }
607
608    /**
609     * Returns the favorite list, or creates it if not available.<p>
610     *
611     * @param cms the cms context
612     *
613     * @return the favorite list
614     *
615     * @throws CmsException if something goes wrong
616     */
617    public List<CmsContainerElementBean> getFavoriteList(CmsObject cms) throws CmsException {
618
619        CmsUser user = cms.getRequestContext().getCurrentUser();
620        Object obj = user.getAdditionalInfo(ADDINFO_ADE_FAVORITE_LIST);
621
622        List<CmsContainerElementBean> favList = new ArrayList<CmsContainerElementBean>();
623        if (obj instanceof String) {
624            try {
625                JSONArray array = new JSONArray((String)obj);
626                for (int i = 0; i < array.length(); i++) {
627                    try {
628                        favList.add(elementFromJson(array.getJSONObject(i)));
629                    } catch (Throwable e) {
630                        // should never happen, catches wrong or no longer existing values
631                        LOG.warn(e.getLocalizedMessage());
632                    }
633                }
634            } catch (Throwable e) {
635                // should never happen, catches json parsing
636                LOG.warn(e.getLocalizedMessage());
637            }
638        } else {
639            // save to be better next time
640            saveFavoriteList(cms, favList);
641        }
642
643        return favList;
644    }
645
646    /**
647     * Returns the settings configured for the given formatter which should be editable via ADE.<p>
648     *
649     * @param cms the cms context
650     * @param config the sitemap configuration
651     * @param mainFormatter the formatter
652     * @param res the element resource
653     * @param locale the content locale
654     * @param req the current request, if available
655     *
656     * @return the settings configured for the given formatter
657     */
658    public Map<String, CmsXmlContentProperty> getFormatterSettings(
659        CmsObject cms,
660        CmsADEConfigData config,
661        I_CmsFormatterBean mainFormatter,
662        CmsResource res,
663        Locale locale,
664        ServletRequest req) {
665
666        Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>();
667        Visibility defaultVisibility = Visibility.elementAndParentIndividual;
668        if (mainFormatter != null) {
669            for (Entry<String, CmsXmlContentProperty> entry : mainFormatter.getSettings(config).entrySet()) {
670                Visibility visibility = entry.getValue().getVisibility(defaultVisibility);
671                if (visibility.isVisibleOnElement()) {
672                    result.put(entry.getKey(), entry.getValue());
673                }
674            }
675            if (mainFormatter.hasNestedFormatterSettings()) {
676                List<I_CmsFormatterBean> nestedFormatters = getNestedFormatters(cms, config, res, locale, req);
677                if (nestedFormatters != null) {
678                    for (I_CmsFormatterBean formatter : nestedFormatters) {
679                        for (Entry<String, CmsXmlContentProperty> entry : formatter.getSettings(config).entrySet()) {
680                            Visibility visibility = entry.getValue().getVisibility(defaultVisibility);
681                            switch (visibility) {
682                                case parentShared:
683                                case elementAndParentShared:
684                                    result.put(entry.getKey(), entry.getValue());
685                                    break;
686                                case elementAndParentIndividual:
687                                case parentIndividual:
688                                    String settingName = formatter.getKeyOrId() + "_" + entry.getKey();
689                                    CmsXmlContentProperty settingConf = entry.getValue().withName(settingName);
690                                    result.put(settingName, settingConf);
691                                    break;
692                                default:
693                                    break;
694                            }
695                        }
696                    }
697                }
698            }
699        }
700        return result;
701    }
702
703    /**
704     * Returns the inheritance state for the given inheritance name and resource.<p>
705     *
706     * @param cms the current cms context
707     * @param resource the resource
708     * @param name the inheritance name
709     *
710     * @return the inheritance state
711     */
712    public CmsInheritedContainerState getInheritedContainerState(CmsObject cms, CmsResource resource, String name) {
713
714        String rootPath = resource.getRootPath();
715        if (!resource.isFolder()) {
716            rootPath = CmsResource.getParentFolder(rootPath);
717        }
718        CmsInheritedContainerState result = new CmsInheritedContainerState();
719        boolean online = isOnline(cms);
720        CmsContainerConfigurationCache cache = online
721        ? m_onlineContainerConfigurationCache
722        : m_offlineContainerConfigurationCache;
723        result.addConfigurations(cache, rootPath, name);
724        return result;
725
726    }
727
728    /**
729     * Returns the inheritance state for the given inheritance name and root path.<p>
730     *
731     * @param cms the current cms context
732     * @param rootPath the root path
733     * @param name the inheritance name
734     *
735     * @return the inheritance state
736     *
737     * @throws CmsException if something goes wrong
738     */
739    public CmsInheritedContainerState getInheritedContainerState(CmsObject cms, String rootPath, String name)
740    throws CmsException {
741
742        String oldSiteRoot = cms.getRequestContext().getSiteRoot();
743        try {
744            cms.getRequestContext().setSiteRoot("");
745            CmsResource resource = cms.readResource(rootPath);
746            return getInheritedContainerState(cms, resource, name);
747        } finally {
748            cms.getRequestContext().setSiteRoot(oldSiteRoot);
749        }
750    }
751
752    /**
753     * Gets the maximum sitemap depth.<p>
754     *
755     * @return the maximum sitemap depth
756     */
757    public int getMaxSitemapDepth() {
758
759        return 20;
760    }
761
762    /**
763     * Gets the module configuration resource type.<p>
764     *
765     * @return the module configuration resource type
766     */
767    public I_CmsResourceType getModuleConfigurationType() {
768
769        return m_moduleConfigType;
770    }
771
772    /**
773     * Returns the nested formatters of the given resource.<p>
774     *
775     * @param cms the cms context
776     * @param config the sitemap configuration
777     * @param res the resource
778     * @param locale the content locale
779     * @param req the request, if available
780     *
781     * @return the nested formatters
782     */
783    public List<I_CmsFormatterBean> getNestedFormatters(
784        CmsObject cms,
785        CmsADEConfigData config,
786        CmsResource res,
787        Locale locale,
788        ServletRequest req) {
789
790        List<I_CmsFormatterBean> result = null;
791        if (CmsResourceTypeXmlContent.isXmlContent(res)) {
792            CmsResourceTypeXmlContent type = (CmsResourceTypeXmlContent)OpenCms.getResourceManager().getResourceType(
793                res);
794            String schema = type.getSchema();
795            try {
796                CmsXmlContentDefinition contentDefinition = CmsXmlContentDefinition.unmarshal(cms, schema);
797                // get the content handler for the resource type to create
798                I_CmsXmlContentHandler handler = contentDefinition.getContentHandler();
799                if (handler.hasNestedFormatters()) {
800                    result = new ArrayList<I_CmsFormatterBean>();
801                    for (String formatterId : handler.getNestedFormatters(cms, res, locale, req)) {
802                        I_CmsFormatterBean formatter = config.findFormatter(formatterId);
803                        if (formatter != null) {
804                            result.add(formatter);
805                        }
806                    }
807                }
808            } catch (CmsXmlException e) {
809                LOG.error(e.getMessage(), e);
810            }
811        }
812        return result;
813    }
814
815    /**
816     * Creates a stream that produces the pages/groups referencing a given element.
817     *
818     *  <p>Note that this method doesn't take a CmsObject and just generates all resources referencing the element regardless of whether
819     *  the current user can read them. So readResource calls for the ids of these resources may fail.
820     *
821     * @param resource the element resource
822     * @return the stream of resources which use the element
823     */
824    public Stream<CmsResource> getOfflineElementUses(CmsResource resource) {
825
826        if ((resource == null) || resource.getStructureId().isNullUUID()) {
827            return Stream.of();
828        }
829        try {
830            List<CmsRelation> relations = m_offlineCms.readRelations(
831                CmsRelationFilter.relationsToStructureId(resource.getStructureId()));
832
833            return relations.stream().flatMap(rel -> {
834                try {
835                    CmsResource source = rel.getSource(m_offlineCms, CmsResourceFilter.ALL);
836                    return Stream.of(source);
837                } catch (Exception e) {
838                    LOG.debug(e.getLocalizedMessage(), e);
839                    return Stream.of();
840                }
841            }).filter(source -> {
842                return (CmsResourceTypeXmlContainerPage.isContainerPage(source)
843                    || CmsResourceTypeXmlContainerPage.isModelGroup(source)
844                    || OpenCms.getResourceManager().matchResourceType(
845                        CmsResourceTypeXmlContainerPage.GROUP_CONTAINER_TYPE_NAME,
846                        source.getTypeId()));
847            });
848        } catch (CmsException e) {
849            LOG.error(e.getLocalizedMessage(), e);
850            return Stream.of();
851        }
852    }
853
854    /**
855     * Gets ADE parameters.<p>
856     *
857     * @param cms the current CMS context
858     * @return the ADE parameters for the current user
859     */
860    public Map<String, String> getParameters(CmsObject cms) {
861
862        Map<String, String> result = new LinkedHashMap<String, String>(m_parameters);
863        if (cms != null) {
864            String userParamsStr = (String)(cms.getRequestContext().getCurrentUser().getAdditionalInfo().get(
865                "ADE_PARAMS"));
866            if (userParamsStr != null) {
867                Map<String, String> userParams = CmsStringUtil.splitAsMap(userParamsStr, "|", ":");
868                result.putAll(userParams);
869            }
870        }
871        return result;
872    }
873
874    /**
875     * Gets the content element type for the given path's parent folder.
876     *
877     * @param online true if we want to use the Online project's configuration
878     * @param rootPath the root path of a content
879     *
880     * @return the parent folder type name, or null if none is defined
881     */
882    public String getParentFolderType(boolean online, String rootPath) {
883
884        return getCacheState(online).getParentFolderType(rootPath);
885
886    }
887
888    /**
889     * Returns the permission info for the given resource.<p>
890     *
891     * @param cms the cms context
892     * @param resource the resource
893     * @param contextPath the context path
894     *
895     * @return the permission info
896     *
897     * @throws CmsException if checking the permissions fails
898     */
899    public CmsPermissionInfo getPermissionInfo(CmsObject cms, CmsResource resource, String contextPath)
900    throws CmsException {
901
902        boolean hasView = cms.hasPermissions(
903            resource,
904            CmsPermissionSet.ACCESS_VIEW,
905            false,
906            CmsResourceFilter.ALL.addRequireVisible());
907        boolean hasWrite = false;
908        if (hasView) {
909            try {
910                I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resource.getTypeId());
911                CmsExplorerTypeSettings settings = OpenCms.getWorkplaceManager().getExplorerTypeSetting(
912                    type.getTypeName());
913                hasView = (settings == null)
914                    || settings.getAccess().getPermissions(cms, resource).requiresViewPermission();
915                if (hasView
916                    && CmsResourceTypeXmlContent.isXmlContent(resource)
917                    && !CmsResourceTypeXmlContainerPage.isContainerPage(resource)) {
918                    if (contextPath == null) {
919                        contextPath = resource.getRootPath();
920                    }
921                    CmsResourceTypeConfig localConfigData = lookupConfigurationWithCache(
922                        cms,
923                        contextPath).getResourceType(type.getTypeName());
924                    if (localConfigData != null) {
925                        Map<CmsUUID, CmsElementView> elementViews = getElementViews(cms);
926                        hasView = elementViews.containsKey(localConfigData.getElementView())
927                            && elementViews.get(localConfigData.getElementView()).hasPermission(cms, resource);
928                    }
929                }
930                // the user may only have write permissions if he is allowed to view the resource
931                hasWrite = hasView
932                    && cms.hasPermissions(
933                        resource,
934                        CmsPermissionSet.ACCESS_WRITE,
935                        false,
936                        CmsResourceFilter.IGNORE_EXPIRATION)
937                    && ((settings == null)
938                        || settings.getAccess().getPermissions(cms, resource).requiresWritePermission());
939            } catch (CmsLoaderException e) {
940                LOG.warn(e.getLocalizedMessage(), e);
941                hasWrite = false;
942            }
943        }
944
945        if (hasWrite && isEditorRestricted(cms, resource)) {
946            hasWrite = false;
947        }
948
949        String noEdit = new CmsResourceUtil(cms, resource).getNoEditReason(
950            OpenCms.getWorkplaceManager().getWorkplaceLocale(cms),
951            true);
952
953        boolean isFunction = false;
954        for (String type : new String[] {"function", CmsResourceTypeFunctionConfig.TYPE_NAME}) {
955            if (OpenCms.getResourceManager().matchResourceType(type, resource.getTypeId())) {
956                isFunction = true;
957                break;
958            }
959        }
960        if (isFunction) {
961            Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
962            noEdit = Messages.get().getBundle(locale).key(Messages.GUI_CANT_EDIT_FUNCTIONS_0);
963        }
964
965        return new CmsPermissionInfo(hasView, hasWrite, noEdit);
966    }
967
968    /**
969     * Gets a map of plugin wrappers for the given site path.
970     *
971     * <p>This *only* includes plugins defined in site plugins active on the given path, not those referenced in formatters.
972     *
973     * @param cms the CMS context
974     * @param path the path for which to get the plugins
975     *
976     * @return the map of plugin wrappers, with the plugin groups as keys
977     */
978    public Map<String, List<CmsTemplatePluginWrapper>> getPluginsForPath(CmsObject cms, String path) {
979
980        CmsADEConfigData config = lookupConfigurationWithCache(cms, cms.getRequestContext().addSiteRoot(path));
981
982        Multimap<String, CmsTemplatePlugin> plugins = CmsTemplatePluginFinder.getActiveTemplatePluginsFromSitePlugins(
983            config);
984        Map<String, List<CmsTemplatePluginWrapper>> result = new HashMap<>();
985        for (String key : plugins.keySet()) {
986            List<CmsTemplatePluginWrapper> wrappers = plugins.get(key).stream().map(
987                plugin -> new CmsTemplatePluginWrapper(cms, plugin)).collect(Collectors.toList());
988            result.put(key, Collections.unmodifiableList(wrappers));
989        }
990        return Collections.unmodifiableMap(result);
991
992    }
993
994    /**
995     * Gets the raw configured detail page information, with no existence checks or path correction.
996     *
997     * @param cms the CMS context
998     * @return the list of raw detail page info beans
999     */
1000    public List<CmsDetailPageInfo> getRawDetailPages(CmsObject cms) {
1001
1002        return getCache(cms.getRequestContext().getCurrentProject().isOnlineProject()).getRawDetailPages();
1003    }
1004
1005    /**
1006     * Returns the favorite list, or creates it if not available.<p>
1007     *
1008     * @param cms the cms context
1009     *
1010     * @return the favorite list
1011     *
1012     * @throws CmsException if something goes wrong
1013     */
1014    public List<CmsContainerElementBean> getRecentList(CmsObject cms) throws CmsException {
1015
1016        CmsUser user = cms.getRequestContext().getCurrentUser();
1017        Object obj = user.getAdditionalInfo(ADDINFO_ADE_RECENT_LIST);
1018
1019        List<CmsContainerElementBean> recentList = new ArrayList<CmsContainerElementBean>();
1020        if (obj instanceof String) {
1021            try {
1022                JSONArray array = new JSONArray((String)obj);
1023                for (int i = 0; i < array.length(); i++) {
1024                    try {
1025                        recentList.add(elementFromJson(array.getJSONObject(i)));
1026                    } catch (Throwable e) {
1027                        // should never happen, catches wrong or no longer existing values
1028                        LOG.warn(e.getLocalizedMessage());
1029                    }
1030                }
1031            } catch (Throwable e) {
1032                // should never happen, catches json parsing
1033                LOG.warn(e.getLocalizedMessage());
1034            }
1035        } else {
1036            // save to be better next time
1037            saveRecentList(cms, recentList);
1038        }
1039
1040        return recentList;
1041    }
1042
1043    /**
1044     * Gets a map of sitemap attribute values by sitemap path for a given attribute key.
1045     *
1046     * @param cms the current CMS context
1047     * @param attribute the sitemap attribute key
1048     *
1049     * @return the map of attribute values, with the root paths of the corresponding subsitemaps as keys
1050     */
1051    public Map<String, String> getSitemapAttributeValuesByPath(CmsObject cms, String attribute) {
1052
1053        boolean online = (null == cms) || isOnline(cms);
1054        CmsADEConfigCacheState state = getCacheState(online);
1055        return state.getAttributeValuesByPath(attribute);
1056    }
1057
1058    /**
1059     * Gets the sitemap configuration resource type.<p>
1060     *
1061     * @return the resource type for sitemap configurations
1062     */
1063    public I_CmsResourceType getSitemapConfigurationType() {
1064
1065        return m_configType;
1066    }
1067
1068    /**
1069     * Gets the registered sitemap extra info providers.
1070     *
1071     * @return the list of providers
1072     */
1073    public List<I_CmsSitemapExtraInfoProvider> getSitemapExtraInfoProviders() {
1074
1075        return m_sitemapExtraInfoProviders.stream().sorted(
1076            Comparator.comparing(provider -> provider.getOrder())).collect(Collectors.toList());
1077    }
1078
1079    /**
1080     * Returns all sub sites below the given path.<p>
1081     *
1082     * @param cms the cms context
1083     * @param subSiteRoot the sub site root path
1084     *
1085     * @return the sub site root paths
1086     */
1087    public List<String> getSubSitePaths(CmsObject cms, String subSiteRoot) {
1088
1089        List<String> result = new ArrayList<String>();
1090        String normalizedRootPath = CmsStringUtil.joinPaths("/", subSiteRoot, "/");
1091        CmsADEConfigCacheState state = getCacheState(isOnline(cms));
1092        Set<String> siteConfigurationPaths = state.getSiteConfigurationPaths();
1093        for (String path : siteConfigurationPaths) {
1094            if ((path.length() > normalizedRootPath.length()) && path.startsWith(normalizedRootPath)) {
1095                result.add(path);
1096            }
1097        }
1098        return result;
1099    }
1100
1101    /**
1102     * Tries to get the subsite root for a given resource root path.<p>
1103     *
1104     * @param cms the current CMS context
1105     * @param rootPath the root path for which the subsite root should be found
1106     *
1107     * @return the subsite root
1108     */
1109    public String getSubSiteRoot(CmsObject cms, String rootPath) {
1110
1111        CmsADEConfigData configData = lookupConfiguration(cms, rootPath);
1112        String basePath = configData.getBasePath();
1113        String siteRoot = OpenCms.getSiteManager().getSiteRoot(rootPath);
1114        if (siteRoot == null) {
1115            siteRoot = "";
1116        }
1117        if ((basePath == null) || !basePath.startsWith(siteRoot)) {
1118            // the subsite root should always be below the site root
1119            return siteRoot;
1120        } else {
1121            return basePath;
1122        }
1123    }
1124
1125    /**
1126     * Gets the subsites to be displayed in the site selector.
1127     *
1128     * @param online true if we want the subsites for the Online project
1129     *
1130     * @return the subsites to be displayed in the site selector
1131     */
1132    public List<String> getSubsitesForSiteSelector(boolean online) {
1133
1134        return getCacheState(online).getSubsitesForSiteSelector();
1135
1136    }
1137
1138    /**
1139     * Gets the table of upload warnings.
1140     *
1141     * @return the table of upload warnings
1142     */
1143    public CmsUploadWarningTable getUploadWarningTable() {
1144
1145        return m_uploadWarningTable;
1146    }
1147
1148    /**
1149     * Processes a HTML redirect content.<p>
1150     *
1151     * This needs to be in the ADE manager because the user for whom the HTML redirect is being loaded
1152     * does not necessarily have read permissions for the redirect target, so we read the redirect target
1153     * with admin privileges.<p>
1154     *
1155     * @param userCms the CMS context of the current user
1156     * @param request the servlet request
1157     * @param response the servlet response
1158     * @param htmlRedirect the path of the HTML redirect resource
1159     *
1160     * @throws CmsException if something goes wrong
1161     */
1162    public void handleHtmlRedirect(
1163        CmsObject userCms,
1164        HttpServletRequest request,
1165        HttpServletResponse response,
1166        String htmlRedirect)
1167    throws CmsException {
1168
1169        CmsObject cms = OpenCms.initCmsObject(m_offlineCms);
1170        CmsRequestContext userContext = userCms.getRequestContext();
1171        CmsRequestContext currentContext = cms.getRequestContext();
1172        currentContext.setCurrentProject(userContext.getCurrentProject());
1173        currentContext.setSiteRoot(userContext.getSiteRoot());
1174        currentContext.setLocale(userContext.getLocale());
1175        currentContext.setUri(userContext.getUri());
1176
1177        CmsFile file = cms.readFile(htmlRedirect);
1178        CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
1179
1180        // find out the locale to use for reading values from the redirect
1181        List<Locale> candidates = new ArrayList<Locale>();
1182        candidates.add(currentContext.getLocale());
1183        candidates.add(CmsLocaleManager.getDefaultLocale());
1184        candidates.add(Locale.ENGLISH);
1185        candidates.addAll(content.getLocales());
1186        Locale contentLocale = currentContext.getLocale();
1187        for (Locale candidateLocale : candidates) {
1188            if (content.hasLocale(candidateLocale)) {
1189                contentLocale = candidateLocale;
1190                break;
1191            }
1192        }
1193
1194        String typeValue = content.getValue(N_TYPE, contentLocale).getStringValue(cms);
1195        String lnkUri = "";
1196        Integer errorCode;
1197        if ("sublevel".equals(typeValue)) {
1198            // use the nav builder to get the first sub level entry
1199            CmsJspNavBuilder navBuilder = new CmsJspNavBuilder(cms);
1200            if (navBuilder.getNavigationForFolder().size() > 0) {
1201                CmsJspNavElement target = navBuilder.getNavigationForFolder().get(0);
1202                lnkUri = CmsJspTagLink.linkTagAction(target.getResourceName(), request);
1203                errorCode = Integer.valueOf(HttpServletResponse.SC_MOVED_TEMPORARILY);
1204            } else {
1205                // send error 404 if no sub entry available
1206                errorCode = Integer.valueOf(HttpServletResponse.SC_NOT_FOUND);
1207            }
1208        } else {
1209            I_CmsXmlContentValue contentValue = content.getValue(N_LINK, contentLocale);
1210            if (contentValue != null) {
1211                String linkValue = contentValue.getStringValue(cms);
1212                lnkUri = OpenCms.getLinkManager().substituteLinkForUnknownTarget(cms, linkValue);
1213                try {
1214                    errorCode = Integer.valueOf(typeValue);
1215                } catch (NumberFormatException e) {
1216                    LOG.error(e.getMessage(), e);
1217                    // fall back to default
1218                    errorCode = Integer.valueOf(307);
1219                }
1220            } else {
1221                // send error 404 if no link value is set
1222                errorCode = Integer.valueOf(HttpServletResponse.SC_NOT_FOUND);
1223            }
1224        }
1225        if (!currentContext.getCurrentProject().isOnlineProject()) {
1226            // permanent redirects are confusing and not useful in the Offline project because they are stored
1227            // by the browser based on the host name, not the site the user is working in.
1228            if (errorCode.intValue() == HttpServletResponse.SC_MOVED_PERMANENTLY) {
1229                errorCode = Integer.valueOf(HttpServletResponse.SC_MOVED_TEMPORARILY);
1230            }
1231        }
1232        request.setAttribute(CmsRequestUtil.ATTRIBUTE_ERRORCODE, errorCode);
1233        response.setHeader("Location", CmsEncoder.convertHostToPunycode(lnkUri));
1234        response.setHeader("Connection", "close");
1235        response.setStatus(errorCode.intValue());
1236    }
1237
1238    /**
1239     * Initializes the configuration by reading all configuration files and caching their data.<p>
1240     */
1241    public synchronized void initialize() {
1242
1243        // no need to try initialization in case the 'org.opencms.base' is not present and the contained resource types missing
1244        if ((m_initStatus == Status.notInitialized) && OpenCms.getModuleManager().hasModule(MODULE_NAME_ADE_CONFIG)) {
1245            try {
1246                CmsLog.INIT.info(". Initializing the ADE configuration, this may take a while...");
1247                m_initStatus = Status.initializing;
1248                m_configType = OpenCms.getResourceManager().getResourceType(CONFIG_TYPE);
1249                m_moduleConfigType = OpenCms.getResourceManager().getResourceType(MODULE_CONFIG_TYPE);
1250                m_elementViewType = OpenCms.getResourceManager().getResourceType(ELEMENT_VIEW_TYPE);
1251                CmsProject temp = getTempfileProject(m_onlineCms);
1252                m_offlineCms = OpenCms.initCmsObject(m_onlineCms);
1253                m_offlineCms.getRequestContext().setCurrentProject(temp);
1254                m_onlineCache = new CmsConfigurationCache(
1255                    m_onlineCms,
1256                    m_configType,
1257                    m_moduleConfigType,
1258                    m_elementViewType);
1259                m_offlineCache = new CmsConfigurationCache(
1260                    m_offlineCms,
1261                    m_configType,
1262                    m_moduleConfigType,
1263                    m_elementViewType);
1264                CmsLog.INIT.info(". Reading online configuration...");
1265                m_onlineCache.initialize();
1266                CmsLog.INIT.info(". Reading offline configuration...");
1267                m_offlineCache.initialize();
1268                m_onlineContainerConfigurationCache = new CmsContainerConfigurationCache(
1269                    m_onlineCms,
1270                    "online inheritance groups");
1271                m_offlineContainerConfigurationCache = new CmsContainerConfigurationCache(
1272                    m_offlineCms,
1273                    "offline inheritance groups");
1274                CmsLog.INIT.info(". Reading online inherited container configurations...");
1275                m_onlineContainerConfigurationCache.initialize();
1276                CmsLog.INIT.info(". Reading offline inherited container configurations...");
1277                m_offlineContainerConfigurationCache.initialize();
1278                m_offlineFormatterCache = new CmsFormatterConfigurationCache(m_offlineCms, "offline formatters");
1279                m_onlineFormatterCache = new CmsFormatterConfigurationCache(m_onlineCms, "online formatters");
1280                CmsLog.INIT.info(". Reading online formatter configurations...");
1281                m_onlineFormatterCache.initialize();
1282                CmsLog.INIT.info(". Reading offline formatter configurations...");
1283                m_offlineFormatterCache.initialize();
1284
1285                m_offlineDetailIdCache = new CmsDetailNameCache(m_offlineCms);
1286                m_onlineDetailIdCache = new CmsDetailNameCache(m_onlineCms);
1287                CmsLog.INIT.info(". Initializing online detail name cache...");
1288                m_onlineDetailIdCache.initialize();
1289                CmsLog.INIT.info(". Initializing offline detail name cache...");
1290                m_offlineDetailIdCache.initialize();
1291
1292                CmsGlobalConfigurationCacheEventHandler handler = new CmsGlobalConfigurationCacheEventHandler(
1293                    m_onlineCms);
1294                handler.addCache(m_offlineCache, m_onlineCache, "ADE configuration cache");
1295                handler.addCache(
1296                    m_offlineContainerConfigurationCache,
1297                    m_onlineContainerConfigurationCache,
1298                    "Inherited container cache");
1299                handler.addCache(m_offlineFormatterCache, m_onlineFormatterCache, "formatter configuration cache");
1300                handler.addCache(m_offlineDetailIdCache, m_onlineDetailIdCache, "Detail ID cache");
1301                OpenCms.getEventManager().addCmsEventListener(handler);
1302                CmsLog.INIT.info(". Done initializing the ADE configuration.");
1303                m_initStatus = Status.initialized;
1304            } catch (CmsException e) {
1305                m_initStatus = Status.notInitialized;
1306                LOG.error(e.getLocalizedMessage(), e);
1307            }
1308            m_detailPageHandler.initialize(m_offlineCms, m_onlineCms);
1309        }
1310    }
1311
1312    /**
1313     * Checks whether the given resource is configured as a detail page.<p>
1314     *
1315     * @param cms the current CMS context
1316     * @param resource the resource which should be tested
1317     *
1318     * @return true if the resource is configured as a detail page
1319     */
1320    public boolean isDetailPage(CmsObject cms, CmsResource resource) {
1321
1322        return getCache(isOnline(cms)).isDetailPage(cms, resource);
1323    }
1324
1325    /**
1326     * Checks if the user should be prevented from editing a file.
1327     *
1328     * <p>This is not a permission check, but an additional mechanism to prevent users from editing configuration files even if they technically need or have write permissions for these files.
1329     *
1330     * @param cms the CMS context
1331     * @param res the resource to check
1332     * @return true if the user should be prevented from editing the file
1333     */
1334    public boolean isEditorRestricted(CmsObject cms, CmsResource res) {
1335
1336        if (OpenCms.getResourceManager().matchResourceType(CONFIG_TYPE, res.getTypeId())) {
1337            CmsRole role = getRoleForSitemapConfigEditing();
1338            if ((role != null) && !OpenCms.getRoleManager().hasRoleForResource(cms, role, res)) {
1339                return true;
1340            }
1341        }
1342        return false;
1343    }
1344
1345    /**
1346     * Checks if an element is reused in a page or group that is not excluded by a given predicate.
1347     *
1348     * @param resource the resource to check
1349     * @param exclude predicate used to ignore reuses which match it
1350     * @return true if the element is reused
1351     */
1352    public boolean isElementReused(CmsResource resource, Predicate<CmsResource> exclude) {
1353
1354        return getOfflineElementUses(resource).anyMatch(source -> !exclude.test(source));
1355    }
1356
1357    /**
1358     * Checks whether the ADE manager is initialized (this should usually be the case except during the setup).<p>
1359     *
1360     * @return true if the ADE manager is initialized
1361     */
1362    public boolean isInitialized() {
1363
1364        return m_initStatus == Status.initialized;
1365    }
1366
1367    /**
1368     * Returns the show editor help flag.<p>
1369     *
1370     * @param cms the cms context
1371     *
1372     * @return the show editor help flag
1373     */
1374    public boolean isShowEditorHelp(CmsObject cms) {
1375
1376        CmsUser user = cms.getRequestContext().getCurrentUser();
1377        String showHelp = (String)user.getAdditionalInfo(ADDINFO_ADE_SHOW_EDITOR_HELP);
1378        return CmsStringUtil.isEmptyOrWhitespaceOnly(showHelp) || Boolean.parseBoolean(showHelp);
1379    }
1380
1381    /**
1382     * Looks up the configuration data for a given sitemap path.<p>
1383     *
1384     * @param cms the current CMS context
1385     * @param rootPath the root path for which the configuration data should be looked up
1386     *
1387     * @return the configuration data
1388     */
1389    public CmsADEConfigData lookupConfiguration(CmsObject cms, String rootPath) {
1390
1391        CmsADEConfigData configData = internalLookupConfiguration(cms, rootPath);
1392        return configData;
1393    }
1394
1395    /**
1396     * Looks up the configuration data for a given sitemap path, but uses a thread-local cache for the current request for efficiency.
1397     *
1398     * @param cms the current CMS context
1399     * @param rootPath the root path for which the configuration data should be looked up
1400     *
1401     * @return the configuration data
1402     */
1403    public CmsADEConfigData lookupConfigurationWithCache(CmsObject cms, String rootPath) {
1404
1405        boolean online = (cms == null) || cms.getRequestContext().getCurrentProject().isOnlineProject();
1406        String cacheKey = "" + online + ":" + rootPath;
1407        OpenCmsServlet.RequestCache context = OpenCmsServlet.getRequestCache();
1408        CmsADEConfigData result = null;
1409        if (context != null) {
1410            result = context.getCachedConfig(cacheKey);
1411        }
1412        if (result == null) {
1413            result = internalLookupConfiguration(cms, rootPath);
1414            if (context != null) {
1415                context.setCachedConfig(cacheKey, result);
1416            }
1417        }
1418        return result;
1419    }
1420
1421    /**
1422     * Reloads the configuration.<p>
1423     *
1424     * Normally you shouldn't call this directly since the event handlers take care of updating the configuration.
1425     */
1426    public void refresh() {
1427
1428        m_onlineCache.initialize();
1429        m_offlineCache.initialize();
1430    }
1431
1432    public void removeSitemapExtraInfoProvider(I_CmsSitemapExtraInfoProvider provider) {
1433
1434        m_sitemapExtraInfoProviders.remove(provider);
1435    }
1436
1437    /**
1438     * Saves a list of detail pages.<p>
1439     * @param cms the cms context
1440     * @param rootPath the root path
1441     * @param detailPages the detail pages
1442     * @param newId the id to use for new detail pages without an id
1443     * @return true if the detail pages could be successfully saved
1444     *
1445     * @throws CmsException if something goes wrong
1446     */
1447    public boolean saveDetailPages(CmsObject cms, String rootPath, List<CmsDetailPageInfo> detailPages, CmsUUID newId)
1448    throws CmsException {
1449
1450        CmsADEConfigData configData = lookupConfiguration(cms, rootPath);
1451        CmsDetailPageConfigurationWriter configWriter;
1452        String originalSiteRoot = cms.getRequestContext().getSiteRoot();
1453        try {
1454            cms.getRequestContext().setSiteRoot("");
1455            if (configData.isModuleConfiguration()) {
1456                return false;
1457            }
1458            CmsResource configFile = configData.getResource();
1459            configWriter = new CmsDetailPageConfigurationWriter(cms, configFile);
1460            configWriter.updateAndSave(detailPages, newId);
1461            return true;
1462        } finally {
1463            cms.getRequestContext().setSiteRoot(originalSiteRoot);
1464        }
1465    }
1466
1467    /**
1468     * Saves the favorite list, user based.<p>
1469     *
1470     * @param cms the cms context
1471     * @param favoriteList the element list
1472     *
1473     * @throws CmsException if something goes wrong
1474     */
1475    public void saveFavoriteList(CmsObject cms, List<CmsContainerElementBean> favoriteList) throws CmsException {
1476
1477        saveElementList(cms, favoriteList, ADDINFO_ADE_FAVORITE_LIST);
1478    }
1479
1480    /**
1481     * Saves the inheritance container information.<p>
1482     *
1483     * @param cms the current cms context
1484     * @param pageResource the resource or parent folder
1485     * @param name the inheritance name
1486     * @param newOrder if the element have been reordered
1487     * @param elements the elements
1488     *
1489     * @throws CmsException if something goes wrong
1490     */
1491    public void saveInheritedContainer(
1492        CmsObject cms,
1493        CmsResource pageResource,
1494        String name,
1495        boolean newOrder,
1496        List<CmsContainerElementBean> elements)
1497    throws CmsException {
1498
1499        CmsContainerConfigurationWriter writer = new CmsContainerConfigurationWriter();
1500        writer.save(cms, name, newOrder, pageResource, elements);
1501
1502        // Inheritance groups are usually reloaded directly after saving them,
1503        // so the cache needs to be up to date after this method is called
1504        m_offlineContainerConfigurationCache.flushUpdates();
1505    }
1506
1507    /**
1508     * Saves the inheritance container information.<p>
1509     *
1510     * @param cms the current cms context
1511     * @param sitePath the site path of the resource or parent folder
1512     * @param name the inheritance name
1513     * @param newOrder if the element have been reordered
1514     * @param elements the elements
1515     *
1516     * @throws CmsException if something goes wrong
1517     */
1518    public void saveInheritedContainer(
1519        CmsObject cms,
1520        String sitePath,
1521        String name,
1522        boolean newOrder,
1523        List<CmsContainerElementBean> elements)
1524    throws CmsException {
1525
1526        saveInheritedContainer(cms, cms.readResource(sitePath), name, newOrder, elements);
1527    }
1528
1529    /**
1530     * Saves the favorite list, user based.<p>
1531     *
1532     * @param cms the cms context
1533     * @param recentList the element list
1534     *
1535     * @throws CmsException if something goes wrong
1536     */
1537    public void saveRecentList(CmsObject cms, List<CmsContainerElementBean> recentList) throws CmsException {
1538
1539        saveElementList(cms, recentList, ADDINFO_ADE_RECENT_LIST);
1540    }
1541
1542    /**
1543     * Sets the show editor help flag.<p>
1544     *
1545     * @param cms the cms context
1546     * @param showHelp the show help flag
1547     * @throws CmsException if writing the user info fails
1548     */
1549    public void setShowEditorHelp(CmsObject cms, boolean showHelp) throws CmsException {
1550
1551        CmsUser user = cms.getRequestContext().getCurrentUser();
1552        user.setAdditionalInfo(ADDINFO_ADE_SHOW_EDITOR_HELP, String.valueOf(showHelp));
1553        cms.writeUser(user);
1554    }
1555
1556    /**
1557     * The method which is called when the OpenCms instance is shut down.<p>
1558     */
1559    public void shutdown() {
1560
1561        // do nothing
1562    }
1563
1564    /**
1565     * Waits until the next time the cache is updated.<p>
1566     *
1567     * @param online true if we want to wait for the online cache, false for the offline cache
1568     */
1569    public void waitForCacheUpdate(boolean online) {
1570
1571        getCache(online).getWaitHandleForUpdateTask().enter(2 * CmsConfigurationCache.TASK_DELAY_MILLIS);
1572    }
1573
1574    /**
1575     * Waits until the formatter cache has finished updating itself.<p>
1576     *
1577     * This method is only intended for use in test cases.
1578     *
1579     * @param online true if we should wait for the online formatter cache,false for the offline cache
1580     */
1581    public void waitForFormatterCache(boolean online) {
1582
1583        CmsFormatterConfigurationCache cache = online ? m_onlineFormatterCache : m_offlineFormatterCache;
1584        cache.waitForUpdate();
1585    }
1586
1587    /**
1588     * Creates an element from its serialized data.<p>
1589     *
1590     * @param data the serialized data
1591     *
1592     * @return the restored element bean
1593     *
1594     * @throws JSONException if the serialized data got corrupted
1595     */
1596    protected CmsContainerElementBean elementFromJson(JSONObject data) throws JSONException {
1597
1598        CmsUUID element = new CmsUUID(data.getString(FavListProp.ELEMENT.name().toLowerCase()));
1599        CmsUUID formatter = null;
1600        if (data.has(FavListProp.FORMATTER.name().toLowerCase())) {
1601            formatter = new CmsUUID(data.getString(FavListProp.FORMATTER.name().toLowerCase()));
1602        }
1603        Map<String, String> properties = new HashMap<String, String>();
1604
1605        JSONObject props = data.getJSONObject(FavListProp.PROPERTIES.name().toLowerCase());
1606        Iterator<String> keys = props.keys();
1607        while (keys.hasNext()) {
1608            String key = keys.next();
1609            properties.put(key, props.getString(key));
1610        }
1611
1612        return new CmsContainerElementBean(element, formatter, properties, false);
1613    }
1614
1615    /**
1616     * Converts the given element to JSON.<p>
1617     *
1618     * @param element the element to convert
1619     * @param excludeSettings the keys of settings which should not be written to the JSON
1620     *
1621     * @return the JSON representation
1622     */
1623    protected JSONObject elementToJson(CmsContainerElementBean element, Set<String> excludeSettings) {
1624
1625        JSONObject data = null;
1626        try {
1627            data = new JSONObject();
1628            data.put(FavListProp.ELEMENT.name().toLowerCase(), element.getId().toString());
1629            if (element.getFormatterId() != null) {
1630                data.put(FavListProp.FORMATTER.name().toLowerCase(), element.getFormatterId().toString());
1631            }
1632            JSONObject properties = new JSONObject();
1633            for (Map.Entry<String, String> entry : element.getIndividualSettings().entrySet()) {
1634                String settingKey = entry.getKey();
1635                if (!excludeSettings.contains(settingKey)) {
1636                    properties.put(entry.getKey(), entry.getValue());
1637                }
1638            }
1639            data.put(FavListProp.PROPERTIES.name().toLowerCase(), properties);
1640        } catch (JSONException e) {
1641            // should never happen
1642            if (!LOG.isDebugEnabled()) {
1643                LOG.warn(e.getLocalizedMessage());
1644            }
1645            LOG.debug(e.getLocalizedMessage(), e);
1646            return null;
1647        }
1648        return data;
1649    }
1650
1651    /**
1652     * Gets the configuration cache instance.<p>
1653     *
1654     * @param online true if you want the online cache, false for the offline cache
1655     *
1656     * @return the ADE configuration cache instance
1657     */
1658    protected CmsConfigurationCache getCache(boolean online) {
1659
1660        return online ? m_onlineCache : m_offlineCache;
1661    }
1662
1663    /**
1664     * Gets the offline cache.<p>
1665     *
1666     * @return the offline configuration cache
1667     */
1668    protected CmsConfigurationCache getOfflineCache() {
1669
1670        return m_offlineCache;
1671    }
1672
1673    /**
1674     * Gets the online cache.<p>
1675     *
1676     * @return the online configuration cache
1677     */
1678    protected CmsConfigurationCache getOnlineCache() {
1679
1680        return m_onlineCache;
1681    }
1682
1683    /**
1684     * Gets the role necessary to edit sitemap configuration files.
1685     *
1686     * @return the role needed for editing sitemap configurations
1687     */
1688    protected CmsRole getRoleForSitemapConfigEditing() {
1689
1690        String roleName = OpenCms.getWorkplaceManager().getSitemapConfigEditRole();
1691        if (roleName == null) {
1692            return null;
1693        } else {
1694            if (roleName.indexOf("/") == -1) {
1695                return CmsRole.valueOfRoleName(roleName).forOrgUnit(null);
1696            } else {
1697                return CmsRole.valueOfRoleName(roleName);
1698            }
1699        }
1700    }
1701
1702    /**
1703     * Gets the root path for a given resource structure id.<p>
1704     *
1705     * @param structureId the structure id
1706     * @param online if true, the resource will be looked up in the online project ,else in the offline project
1707     *
1708     * @return the root path for the given structure id
1709     *
1710     * @throws CmsException if something goes wrong
1711     */
1712    protected String getRootPath(CmsUUID structureId, boolean online) throws CmsException {
1713
1714        CmsConfigurationCache cache = online ? m_onlineCache : m_offlineCache;
1715        return cache.getPathForStructureId(structureId);
1716    }
1717
1718    /**
1719     * Gets a tempfile project, creating one if it doesn't exist already.<p>
1720     *
1721     * @param cms the CMS context to use
1722     * @return the tempfile project
1723     *
1724     * @throws CmsException if something goes wrong
1725     */
1726    protected CmsProject getTempfileProject(CmsObject cms) throws CmsException {
1727
1728        try {
1729            return cms.readProject(I_CmsProjectDriver.TEMP_FILE_PROJECT_NAME);
1730        } catch (CmsException e) {
1731            return cms.createTempfileProject();
1732        }
1733    }
1734
1735    /**
1736     * Internal configuration lookup method.<p>
1737     *
1738     * @param cms the cms context
1739     * @param rootPath the root path for which to look up the configuration
1740     *
1741     * @return the configuration for the given path
1742     */
1743    protected CmsADEConfigData internalLookupConfiguration(CmsObject cms, String rootPath) {
1744
1745        boolean online = (null == cms) || isOnline(cms);
1746        CmsADEConfigCacheState state = getCacheState(online);
1747        return state.lookupConfiguration(rootPath);
1748    }
1749
1750    /**
1751     * Returns true if the project set in the CmsObject is the Online project.<p>
1752     *
1753     * @param cms the CMS context to check
1754     *
1755     * @return true if the project set in the CMS context is the Online project
1756     */
1757    private boolean isOnline(CmsObject cms) {
1758
1759        return cms.getRequestContext().getCurrentProject().isOnlineProject();
1760    }
1761
1762    /**
1763     * Saves an element list to the user additional infos.<p>
1764     *
1765     * @param cms the cms context
1766     * @param elementList the element list
1767     * @param listKey the list key
1768     *
1769     * @throws CmsException if something goes wrong
1770     */
1771    private void saveElementList(CmsObject cms, List<CmsContainerElementBean> elementList, String listKey)
1772    throws CmsException {
1773
1774        // limit the favorite list size to avoid the additional info size limit
1775        if (elementList.size() > DEFAULT_ELEMENT_LIST_SIZE) {
1776            elementList = elementList.subList(0, DEFAULT_ELEMENT_LIST_SIZE);
1777        }
1778
1779        JSONArray data = new JSONArray();
1780
1781        Set<String> excludedSettings = new HashSet<String>();
1782        // do not store the template contexts, since dragging an element into the page which might be invisible
1783        // doesn't make sense
1784        excludedSettings.add(CmsTemplateContextInfo.SETTING);
1785
1786        for (CmsContainerElementBean element : elementList) {
1787            data.put(elementToJson(element, excludedSettings));
1788        }
1789        CmsUser user = cms.getRequestContext().getCurrentUser();
1790        user.setAdditionalInfo(listKey, data.toString());
1791        cms.writeUser(user);
1792    }
1793}