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