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