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}