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