001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (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.xml.containerpage;
029
030import org.opencms.ade.configuration.CmsADEConfigData;
031import org.opencms.ade.configuration.CmsElementView;
032import org.opencms.ade.containerpage.shared.CmsContainer;
033import org.opencms.ade.galleries.shared.CmsGallerySearchBean;
034import org.opencms.ade.sitemap.shared.CmsSitemapData.EditorMode;
035import org.opencms.configuration.preferences.CmsElementViewPreference;
036import org.opencms.file.CmsObject;
037import org.opencms.jsp.util.CmsJspStandardContextBean.TemplateBean;
038import org.opencms.main.CmsLog;
039import org.opencms.util.CmsUUID;
040import org.opencms.workplace.CmsWorkplace;
041import org.opencms.xml.content.CmsXmlContent;
042
043import java.util.ArrayList;
044import java.util.Arrays;
045import java.util.HashMap;
046import java.util.HashSet;
047import java.util.List;
048import java.util.Map;
049import java.util.Set;
050import java.util.concurrent.ConcurrentHashMap;
051
052import javax.servlet.http.HttpServletRequest;
053
054import org.apache.commons.logging.Log;
055
056/**
057 * ADE's session cache.<p>
058 *
059 * @since 8.0.0
060 */
061public final class CmsADESessionCache {
062
063    /**
064     * Stores information about the container page which was last edited, so we can jump back to it later.<p>
065     */
066    public static class LastPageBean {
067
068        /** The detail id (may be null). */
069        private CmsUUID m_detailId;
070
071        /** The page structure id. */
072        private CmsUUID m_pageId;
073
074        /** The site root. */
075        private String m_siteRoot;
076
077        /**
078         * Creates a new instance.<p>
079         *
080         * @param siteRoot the site root
081         * @param pageId the page id
082         * @param detailId the detail content id (may be null)
083         */
084        public LastPageBean(String siteRoot, CmsUUID pageId, CmsUUID detailId) {
085
086            super();
087            m_siteRoot = siteRoot;
088            m_pageId = pageId;
089            m_detailId = detailId;
090        }
091
092        /**
093         * Returns the detailId.<p>
094         *
095         * @return the detailId
096         */
097        public CmsUUID getDetailId() {
098
099            return m_detailId;
100        }
101
102        /**
103         * Returns the pageId.<p>
104         *
105         * @return the pageId
106         */
107        public CmsUUID getPageId() {
108
109            return m_pageId;
110        }
111
112        /**
113         * Returns the siteRoot.<p>
114         *
115         * @return the siteRoot
116         */
117        public String getSiteRoot() {
118
119            return m_siteRoot;
120        }
121
122    }
123
124    /** Session attribute name constant. */
125    public static final String SESSION_ATTR_ADE_CACHE = "__OCMS_ADE_CACHE__";
126
127    /** The log instance for this class. */
128    private static final Log LOG = CmsLog.getLog(CmsADESessionCache.class);
129
130    /** The list size for recently used formatters. */
131    private static final int RECENT_FORMATTERS_SIZE = 10;
132
133    /** The container elements. */
134    private Map<String, CmsContainerElementBean> m_containerElements;
135
136    /** The current values of dynamically loaded attributes in the Acacia editor. */
137    private Map<String, String> m_dynamicValues;
138
139    /** The current element view id. */
140    private CmsUUID m_elementView;
141
142    /** Flag which controls whether small elements should be shown. */
143    private boolean m_isEditSmallElements;
144
145    /** Bean containing last page info. */
146    private LastPageBean m_lastPage;
147
148    /** The last stored gallery search for the page editor. */
149    private CmsGallerySearchBean m_lastPageEditorGallerySearch;
150
151    /** The recently used formatters by resource type. */
152    private Map<String, List<String>> m_recentFormatters = new ConcurrentHashMap<String, List<String>>();
153
154    /** The sitemap editor mode. */
155    private EditorMode m_sitemapEditorMode;
156
157    /** Template bean cache. */
158    private Map<String, TemplateBean> m_templateBeanCache = new HashMap<String, TemplateBean>();
159
160    /** The tool-bar visibility flag. */
161    private boolean m_toolbarVisible;
162
163    /** The cached XML content documents by structure id. */
164    private Map<CmsUUID, CmsXmlContent> m_xmlContents;
165
166    /**
167     * Initializes the session cache.<p>
168     *
169     * @param cms the cms context
170     * @param request the current request
171     */
172    protected CmsADESessionCache(CmsObject cms, HttpServletRequest request) {
173
174        // container element cache
175        m_containerElements = new ConcurrentHashMap<String, CmsContainerElementBean>();
176
177        // XML content cache, used during XML content edit
178        m_xmlContents = new ConcurrentHashMap<CmsUUID, CmsXmlContent>();
179
180        String elementView = null;
181        // within the test cases the request will be null
182        if (request != null) {
183            elementView = CmsWorkplace.getWorkplaceSettings(cms, request).getUserSettings().getAdditionalPreference(
184                CmsElementViewPreference.PREFERENCE_NAME,
185                false);
186        }
187        if (elementView == null) {
188            // use the default element view
189            m_elementView = CmsElementView.DEFAULT_ELEMENT_VIEW.getId();
190        } else {
191            try {
192                m_elementView = new CmsUUID(elementView);
193            } catch (NumberFormatException e) {
194                // use the default element view
195                m_elementView = CmsElementView.DEFAULT_ELEMENT_VIEW.getId();
196                LOG.warn("Malformed element view id '" + elementView + "'.", e);
197            }
198        }
199        // toolbar should be visible initially
200        m_toolbarVisible = true;
201    }
202
203    /**
204     * Gets the session cache for the current session.<p>
205     * In case the request is not editable, <code>null</code> will be returned.<p>
206     *
207     * @param request the current request
208     * @param cms the current CMS context
209     *
210     * @return the ADE session cache for the current session
211     */
212    public static CmsADESessionCache getCache(HttpServletRequest request, CmsObject cms) {
213
214        CmsADESessionCache cache = (CmsADESessionCache)request.getSession().getAttribute(
215            CmsADESessionCache.SESSION_ATTR_ADE_CACHE);
216        if (cache == null) {
217            cache = new CmsADESessionCache(cms, request);
218            request.getSession().setAttribute(CmsADESessionCache.SESSION_ATTR_ADE_CACHE, cache);
219        }
220        return cache;
221    }
222
223    /**
224     * Adds the formatter id to the recently used list for the given type.<p>
225     *
226     * @param resType the resource type
227     * @param keyOrId the formatter id
228     */
229    public void addRecentFormatter(String resType, String keyOrId) {
230
231        List<String> formatterIds = m_recentFormatters.get(resType);
232        if (formatterIds == null) {
233            formatterIds = new ArrayList<String>();
234            m_recentFormatters.put(resType, formatterIds);
235        }
236        formatterIds.remove(keyOrId);
237        if (formatterIds.size() >= (RECENT_FORMATTERS_SIZE)) {
238            formatterIds.remove(RECENT_FORMATTERS_SIZE - 1);
239        }
240        formatterIds.add(0, keyOrId);
241    }
242
243    /**
244     * Clear the cache values that are dynamically loaded in the Acacia content editor.
245     */
246    public void clearDynamicValues() {
247
248        m_dynamicValues = null;
249    }
250
251    /**
252     * Removes the information about the last edited container page.<p>
253     */
254    public void clearLastPage() {
255
256        m_lastPage = null;
257    }
258
259    /**
260     * Returns the cached container element under the given key.<p>
261     *
262     * @param key the cache key
263     *
264     * @return  the cached container element or <code>null</code> if not found
265     */
266    public CmsContainerElementBean getCacheContainerElement(String key) {
267
268        return m_containerElements.get(key);
269    }
270
271    /**
272     * Returns the cached XML content document.<p>
273     *
274     * @param structureId the structure id
275     *
276     * @return the XML document
277     */
278    public CmsXmlContent getCacheXmlContent(CmsUUID structureId) {
279
280        return m_xmlContents.get(structureId);
281    }
282
283    /**
284     * Get cached value that is dynamically loaded by the Acacia content editor.
285     *
286     * @param attribute the attribute to load the value to
287     * @return the cached value
288     */
289    public String getDynamicValue(String attribute) {
290
291        return null == m_dynamicValues ? null : m_dynamicValues.get(attribute);
292    }
293
294    /**
295     * Returns the current element view id.<p>
296     *
297     * @return the current element view id
298     */
299    public CmsUUID getElementView() {
300
301        return m_elementView;
302    }
303
304    /**
305     * Returns the lastPage.<p>
306     *
307     * @return the lastPage
308     */
309    public LastPageBean getLastPage() {
310
311        return m_lastPage;
312    }
313
314    /**
315     * Returns the lastPageEditorGallerySearch.<p>
316     *
317     * @return the lastPageEditorGallerySearch
318     */
319    public CmsGallerySearchBean getLastPageEditorGallerySearch() {
320
321        return m_lastPageEditorGallerySearch;
322    }
323
324    /**
325     * Returns the least recently used matching formatter for the given resource type.<p>
326     *
327     * @param resType the resource type
328     * @param container the container to match
329     * @param config the config data
330     *
331     * @return the formatter if any
332     */
333    public I_CmsFormatterBean getRecentFormatter(String resType, CmsContainer container, CmsADEConfigData config) {
334
335        I_CmsFormatterBean result = null;
336        List<String> formatterKeys = m_recentFormatters.get(resType);
337        if (formatterKeys != null) {
338            Set<String> types = new HashSet<String>(Arrays.asList(container.getType().trim().split(" *, *")));
339            for (String key : formatterKeys) {
340                I_CmsFormatterBean formatter = config.findFormatter(key);
341                if ((formatter != null)
342                    && CmsUUID.isValidUUID(formatter.getId())
343                    && config.getActiveFormatters().containsKey(new CmsUUID(formatter.getId())) // findFormatter may return inactive formatters, but here we only want active ones
344                    && CmsFormatterConfiguration.matchFormatter(formatter, types, container.getWidth())) {
345                    result = formatter;
346                    break;
347                }
348            }
349        }
350
351        return result;
352    }
353
354    /**
355     * Returns the sitemap editor mode.<p>
356     *
357     * @return the sitemap editor mode
358     */
359    public EditorMode getSitemapEditorMode() {
360
361        return m_sitemapEditorMode;
362    }
363
364    /**
365     * Gets the cached template bean for a given container page uri.<p>
366     *
367     * @param uri the container page uri
368     * @param safe if true, return a valid template bean even if it hasn't been cached before
369     *
370     * @return the template bean
371     */
372    public TemplateBean getTemplateBean(String uri, boolean safe) {
373
374        TemplateBean templateBean = m_templateBeanCache.get(uri);
375        if ((templateBean != null) || !safe) {
376            return templateBean;
377        }
378        return new TemplateBean("", "");
379    }
380
381    /**
382     * Returns true if, in this session, a newly opened container page editor window should display edit points for
383     * small elements initially.<p>
384     *
385     * @return true if small elements should be editable initially
386     */
387    public boolean isEditSmallElements() {
388
389        return m_isEditSmallElements;
390    }
391
392    /**
393     * Returns the tool-bar visibility.<p>
394     *
395     * @return the tool-bar visibility
396     */
397    public boolean isToolbarVisible() {
398
399        return m_toolbarVisible;
400    }
401
402    /**
403     * Caches the given container element under the given key.<p>
404     *
405     * @param key the cache key
406     * @param containerElement the object to cache
407     */
408    public void setCacheContainerElement(String key, CmsContainerElementBean containerElement) {
409
410        if ((containerElement != null) && !containerElement.isDoNotCache()) {
411            m_containerElements.put(key, containerElement);
412        }
413    }
414
415    /**
416     * Caches the given XML content document.<p>
417     *
418     * @param structureId the structure id
419     * @param xmlContent the XML document
420     */
421    public void setCacheXmlContent(CmsUUID structureId, CmsXmlContent xmlContent) {
422
423        m_xmlContents.put(structureId, xmlContent);
424    }
425
426    /**
427     * Set cached value for the attribute. Used for dynamically loaded values in the Acacia content editor.
428     *
429     * @param attribute the attribute for which the value should be cached
430     * @param value the value to cache
431     */
432    public void setDynamicValue(String attribute, String value) {
433
434        if (null == m_dynamicValues) {
435            m_dynamicValues = new ConcurrentHashMap<String, String>();
436        }
437        m_dynamicValues.put(attribute, value);
438    }
439
440    /**
441     * Sets the default initial setting for small element editability in this session.<p>
442     *
443     * @param editSmallElements true if small elements should be initially editable
444     */
445    public void setEditSmallElements(boolean editSmallElements) {
446
447        m_isEditSmallElements = editSmallElements;
448    }
449
450    /**
451     * Sets the current element view id.<p>
452     *
453     * @param elementView the current element view id
454     */
455    public void setElementView(CmsUUID elementView) {
456
457        m_elementView = elementView;
458    }
459
460    /**
461     * Stores information about the last edited container page.<p>
462     *
463     * @param cms the CMS context
464     * @param pageId the page id
465     * @param detailId the detail content id
466     */
467    public void setLastPage(CmsObject cms, CmsUUID pageId, CmsUUID detailId) {
468
469        m_lastPage = new LastPageBean(cms.getRequestContext().getSiteRoot(), pageId, detailId);
470
471    }
472
473    /**
474     * Sets the last stored gallery search from the page editor.<p>
475     *
476     * @param searchObj the search to store
477     */
478    public void setLastPageEditorGallerySearch(CmsGallerySearchBean searchObj) {
479
480        m_lastPageEditorGallerySearch = searchObj;
481    }
482
483    /**
484     * Sets the sitemap editor mode.<p>
485     *
486     * @param sitemapEditorMode the sitemap editor mode
487     */
488    public void setSitemapEditorMode(EditorMode sitemapEditorMode) {
489
490        m_sitemapEditorMode = sitemapEditorMode;
491    }
492
493    /**
494     * Caches a template bean for a given container page URI.<p>
495     *
496     * @param uri the container page uri
497     * @param templateBean the template bean to cache
498     */
499    public void setTemplateBean(String uri, TemplateBean templateBean) {
500
501        m_templateBeanCache.put(uri, templateBean);
502    }
503
504    /**
505     * Sets the tool-bar visibility flag.<p>
506     *
507     * @param toolbarVisible the tool-bar visibility to set
508     */
509    public void setToolbarVisible(boolean toolbarVisible) {
510
511        m_toolbarVisible = toolbarVisible;
512    }
513
514    /**
515     * Purges the XML content document by the given id from the cache.<p>
516     *
517     * @param structureId the structure id
518     */
519    public void uncacheXmlContent(CmsUUID structureId) {
520
521        m_xmlContents.remove(structureId);
522        m_dynamicValues = null;
523    }
524}