001/*
002 *  Copyright 2012 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016
017package org.ametys.web.cache.pageelement;
018
019import java.util.Collection;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.Set;
025
026import org.apache.avalon.framework.activity.Disposable;
027import org.apache.avalon.framework.activity.Initializable;
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.configuration.Configurable;
030import org.apache.avalon.framework.configuration.Configuration;
031import org.apache.avalon.framework.configuration.ConfigurationException;
032import org.apache.avalon.framework.context.Context;
033import org.apache.avalon.framework.context.ContextException;
034import org.apache.avalon.framework.context.Contextualizable;
035import org.apache.avalon.framework.service.ServiceException;
036import org.apache.avalon.framework.service.ServiceManager;
037import org.apache.avalon.framework.service.Serviceable;
038import org.apache.cocoon.components.ContextHelper;
039import org.apache.cocoon.environment.Request;
040import org.apache.cocoon.xml.SaxBuffer;
041
042import org.ametys.core.DevMode;
043import org.ametys.core.DevMode.DEVMODE;
044import org.ametys.core.cache.AbstractCacheManager;
045import org.ametys.core.cache.AbstractCacheManager.CacheType;
046import org.ametys.core.cache.Cache;
047import org.ametys.plugins.core.impl.cache.AbstractCacheKey;
048import org.ametys.plugins.core.ui.util.ConfigurationHelper;
049import org.ametys.runtime.i18n.I18nizableText;
050import org.ametys.runtime.plugin.component.AbstractLogEnabled;
051import org.ametys.runtime.plugin.component.PluginAware;
052import org.ametys.web.renderingcontext.RenderingContext;
053
054/**
055 * Cache the page elements renderings.<br>
056 * This class handles several caches, one per site and per JCR workspace.
057 */
058public class PageElementCache extends AbstractLogEnabled implements Component, Contextualizable, Disposable, Serviceable, Initializable, PluginAware, Configurable
059{
060
061    static class PageElementKey extends AbstractCacheKey
062    {
063        PageElementKey(String workspace, String site, String elementType, String elementId, String subelementId, RenderingContext renderingContext)
064        {
065            super(workspace, site, elementType, elementId, subelementId, renderingContext);
066        }
067
068        static PageElementKey of(String workspace, String site, String elementType, String elementId, String subelementId, RenderingContext renderingContext)
069        {
070            return new PageElementKey(workspace, site, elementType, elementId, subelementId, renderingContext);
071        }
072
073        static PageElementKey of(String wspName, String site, String pageElementType, String elementId, String subelementId)
074        {
075            return of(wspName, site, pageElementType, elementId, subelementId, null);
076        }
077
078        static PageElementKey of(String wspName, String site, String pageElementType, String elementId)
079        {
080            return of(wspName, site, pageElementType, elementId, null);
081        }
082
083        static PageElementKey of(String wspName, String site, String pageElementType)
084        {
085            return of(wspName, site, pageElementType, null);
086        }
087
088        static PageElementKey of(String wspName, String site)
089        {
090            return of(wspName, site, null);
091        }
092    }
093
094    /** Avalon role. */
095    public static final String ROLE = PageElementCache.class.getName();
096
097    /** "Disable page element cache" request attribute. */
098    public static final String DISABLE_PE_CACHE_ATTRIBUTE = "disable-page-element-cache";
099
100    // Map<workspace,Map<site, Map<elementType, elementId>>>
101    private Map<String, Map<String, Map<String, String>>> _elementCache = new HashMap<>();
102
103    private Context _context;
104
105    private AbstractCacheManager _cacheManager;
106
107    private String _id;
108
109    private String _pluginName;
110
111    private I18nizableText _label;
112
113    private I18nizableText _description;
114
115    @Override
116    public void service(ServiceManager manager) throws ServiceException
117    {
118        _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE);
119    }
120
121    public void configure(Configuration configuration) throws ConfigurationException
122    {
123        Map<String, Object> parameters = ConfigurationHelper.parsePluginParameters(configuration, _pluginName, getLogger());
124        _label = (I18nizableText) parameters.getOrDefault("label", new I18nizableText(_id));
125        _description = (I18nizableText) parameters.get("description");
126    }
127
128    @Override
129    public void contextualize(Context context) throws ContextException
130    {
131        _context = context;
132    }
133
134    public void setPluginInfo(String pluginName, String featureName, String id)
135    {
136        _id = id;
137        _pluginName = pluginName;
138    }
139
140    public void initialize() throws Exception
141    {
142        _cacheManager.createCache(_id, _label, _description, CacheType.MEMORY, true);
143    }
144
145    /**
146     * Add a content in the cache.
147     * @param workspace the current JCR workspace.
148     * @param site the current site.
149     * @param pageElementType the page element type.
150     * @param elementId the element id (page id or zoneItem id).
151     * @param renderingContext the current rendering context.
152     * @param content the actual content.
153     */
154    public void storePageElement(String workspace, String site, String pageElementType, String elementId, RenderingContext renderingContext, SaxBuffer content)
155    {
156        storePageElement(workspace, site, pageElementType, elementId, "", renderingContext, content);
157    }
158
159    /**
160     * Add a content in the cache.
161     * @param workspace the current JCR workspace.
162     * @param site the current site.
163     * @param pageElementType the page element type.
164     * @param elementId the element id (page id or zoneItem id).
165     * @param subElementId the sub-element id (page id, in case the element id is a zoneItem id).
166     * @param renderingContext the current rendering context.
167     * @param content the actual content.
168     */
169    public void storePageElement(String workspace, String site, String pageElementType, String elementId, String subElementId, RenderingContext renderingContext, SaxBuffer content)
170    {
171        if (isEnabled())
172        {
173            PageElementKey key = new PageElementKey(workspace, site, pageElementType, elementId, subElementId, renderingContext);
174
175            _getCache().put(key, content);
176
177            Map<String, Map<String, String>> workspaceCache = _elementCache.get(workspace);
178            if (workspaceCache == null)
179            {
180                workspaceCache = new HashMap<>();
181                _elementCache.put(workspace, workspaceCache);
182            }
183
184            Map<String, String> siteCache = workspaceCache.get(site);
185            if (siteCache == null)
186            {
187                siteCache = new HashMap<>();
188                workspaceCache.put(site, siteCache);
189            }
190
191            siteCache.put(pageElementType, elementId);
192        }
193    }
194
195    /**
196     * Returns the data in the cache, or null if none.
197     * @param workspace the current JCR workspace.
198     * @param site the current site.
199     * @param pageElementType the element type.
200     * @param elementId the element id (page id or zoneItem id).
201     * @param renderingContext the current rendering context.
202     * @return the stored content.
203     */
204    public SaxBuffer getPageElement(String workspace, String site, String pageElementType, String elementId, RenderingContext renderingContext)
205    {
206        return getPageElement(workspace, site, pageElementType, elementId, "", renderingContext);
207    }
208
209    /**
210     * Returns the data in the cache, or null if none.
211     * @param workspace the current JCR workspace.
212     * @param site the current site.
213     * @param pageElementType the element type.
214     * @param elementId the element id (page id or zoneItem id).
215     * @param subElementId the sub-element id (page id, in case the element id is a zoneItem id).
216     * @param renderingContext the current rendering context.
217     * @return the stored content.
218     */
219    public SaxBuffer getPageElement(String workspace, String site, String pageElementType, String elementId, String subElementId, RenderingContext renderingContext)
220    {
221        if (isEnabled())
222        {
223
224            PageElementKey key = new PageElementKey(workspace, site, pageElementType, elementId, subElementId, renderingContext);
225
226            return _getCache().get(key);
227        }
228
229        return null;
230
231    }
232
233    /**
234     * Returns all stored element types, or null if none.
235     * @param workspace the current JCR workspace.
236     * @param site the current site.
237     * @return all stored element types.
238     */
239    public Set<String> getPageElementTypes(String workspace, String site)
240    {
241
242        if (isEnabled())
243        {
244            Map<String, Map<String, String>> workspaceCache = _elementCache.get(workspace);
245            if (workspaceCache != null)
246            {
247                Map<String, String> siteCache = workspaceCache.get(site);
248                if (siteCache != null)
249                {
250                    return new HashSet<>(siteCache.keySet());
251                }
252            }
253        }
254        return Collections.emptySet();
255    }
256
257    /**
258     * Returns all cached data for a given InputData, or null if none.
259     * @param workspace the current JCR workspace.
260     * @param site the current site.
261     * @param pageElementType the element type.
262     * @return the stored content.
263     */
264    public Collection<String> getElementCache(String workspace, String site, String pageElementType)
265    {
266        if (isEnabled())
267        {
268
269            Map<String, Map<String, String>> workspaceCache = _elementCache.get(workspace);
270            if (workspaceCache != null)
271            {
272                Map<String, String> siteCache = workspaceCache.get(site);
273                if (siteCache != null)
274                {
275                    return siteCache.values();
276                }
277            }
278        }
279
280        return null;
281    }
282
283    /**
284     * Removes a single item from the cache.
285     * @param workspace the target JCR workspace or null to remove from all workspaces.
286     * @param site the target site.
287     * @param pageElementType the element type.
288     * @param elementId the element id.
289     */
290    public void removeItem(String workspace, String site, String pageElementType, String elementId)
291    {
292        if (isEnabled())
293        {
294            if (workspace != null)
295            {
296                Map<String, Map<String, String>> workspaceCache = _elementCache.get(workspace);
297                if (workspaceCache != null)
298                {
299                    Map<String, String> siteCache = workspaceCache.get(site);
300
301                    if (siteCache != null)
302                    {
303
304                        if (siteCache.containsKey(pageElementType))
305                        {
306                            siteCache.put(pageElementType, null);
307                        }
308                    }
309                }
310                this._getCache().invalidate(PageElementKey.of(workspace, site, pageElementType, elementId));
311            }
312            else
313            {
314                // removes from all workspaces
315                for (String wspName : _elementCache.keySet())
316                {
317                    this._getCache().invalidate(PageElementKey.of(wspName, site, pageElementType, elementId));
318                }
319            }
320        }
321    }
322
323    /**
324     * Removes all stored data.
325     */
326    public void clear()
327    {
328        this._elementCache.clear();
329        this._getCache().invalidateAll();
330    }
331
332    /**
333     * Removes all data associated with a JCR workspace and a site.
334     * @param workspace the current JCR workspace. If null, means all workspaces.
335     * @param site the current site.
336     */
337    public void clear(String workspace, String site)
338    {
339        if (workspace != null)
340        {
341            Map<String, Map<String, String>> workspaceCache = _elementCache.get(workspace);
342            if (workspaceCache != null)
343            {
344                workspaceCache.remove(site);
345            }
346            this._getCache().invalidate(PageElementKey.of(workspace, site));
347        }
348        else
349        {
350            _elementCache.forEach((workspaceName, workspaceCache) ->
351            {
352                workspaceCache.remove(site);
353                this._getCache().invalidate(PageElementKey.of(workspaceName, site));
354            });
355        }
356    }
357
358    /**
359     * Removes all data associated with a JCR workspace, a site and a given element type.
360     * @param workspace the current JCR workspace.
361     * @param site the current site.
362     * @param pageElementType the element type.
363     */
364    public void clear(String workspace, String site, String pageElementType)
365    {
366        Map<String, Map<String, String>> workspaceCache = _elementCache.get(workspace);
367        if (workspaceCache != null)
368        {
369            Map<String, String> siteCache = workspaceCache.get(site);
370            
371            if (siteCache != null)
372            {
373                siteCache.remove(pageElementType);
374            }
375        }
376        this._getCache().invalidate(PageElementKey.of(workspace, site, pageElementType));
377    }
378
379    @Override
380    public void dispose()
381    {
382        clear();
383    }
384
385    /**
386     * Test if the cache is enabled.
387     * @return true if the cache is enabled, false otherwise.
388     */
389    protected boolean isEnabled()
390    {
391        Request request = ContextHelper.getRequest(_context);
392        Object disabledRequestStr = request.getAttribute(DISABLE_PE_CACHE_ATTRIBUTE);
393
394        boolean disabledRequest = disabledRequestStr != null && disabledRequestStr.equals("true");
395        DEVMODE developerMode = DevMode.getDeveloperMode(request);
396        boolean disabled = DEVMODE.DEVELOPMENT.equals(developerMode) || DEVMODE.SUPER_DEVELOPPMENT.equals(developerMode);
397        return !disabled && !disabledRequest;
398    }
399
400    private Cache<PageElementKey, SaxBuffer> _getCache()
401    {
402        return _cacheManager.get(_id);
403    }
404
405}