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}