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