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