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 */ 016package org.ametys.web.cache.pageelement; 017 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.Map; 022import java.util.Set; 023import java.util.concurrent.atomic.AtomicInteger; 024 025import org.apache.avalon.framework.activity.Disposable; 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.context.Context; 028import org.apache.avalon.framework.context.ContextException; 029import org.apache.avalon.framework.context.Contextualizable; 030import org.apache.avalon.framework.logger.AbstractLogEnabled; 031import org.apache.cocoon.components.ContextHelper; 032import org.apache.cocoon.environment.Request; 033import org.apache.cocoon.xml.SaxBuffer; 034 035import org.ametys.core.DevMode; 036import org.ametys.core.DevMode.DEVMODE; 037import org.ametys.web.renderingcontext.RenderingContext; 038 039/** 040 * Cache the page elements renderings.<br> 041 * This class handles several caches, one per site and per JCR workspace. 042 */ 043public class PageElementCache extends AbstractLogEnabled implements Component, Contextualizable, Disposable 044{ 045 /** Avalon role. */ 046 public static final String ROLE = PageElementCache.class.getName(); 047 048 /** "Disable page element cache" request attribute. */ 049 public static final String DISABLE_PE_CACHE_ATTRIBUTE = "disable-page-element-cache"; 050 051 // Map<workspace, Map<site, Map<elementType, Map<elementId, Map<subelementId, Map<renderingContext, SaxBuffer>>>>>> 052 private Map<String, Map<String, Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>>>> _cache = new HashMap<>(); 053 054 // Map<workspace, Map<site, Map<elementType, hitCount>>> 055 private Map<String, Map<String, Map<String, AtomicInteger>>> _currentHitCount = new HashMap<>(); 056 057 // Map<workspace, Map<site, Map<elementType, hitCount>>> 058 private Map<String, Map<String, Map<String, AtomicInteger>>> _hitCount = new HashMap<>(); 059 060 // Map<workspace, Map<site, Map<elementType, missCount>>> 061 private Map<String, Map<String, Map<String, AtomicInteger>>> _missCount = new HashMap<>(); 062 063 private Context _context; 064 065 @Override 066 public void contextualize(Context context) throws ContextException 067 { 068 _context = context; 069 } 070 071 /** 072 * Add a content in the cache. 073 * @param workspace the current JCR workspace. 074 * @param site the current site. 075 * @param pageElementType the page element type. 076 * @param elementId the element id (page id or zoneItem id). 077 * @param renderingContext the current rendering context. 078 * @param content the actual content. 079 */ 080 public void storePageElement(String workspace, String site, String pageElementType, String elementId, RenderingContext renderingContext, SaxBuffer content) 081 { 082 storePageElement(workspace, site, pageElementType, elementId, "", renderingContext, content); 083 } 084 085 /** 086 * Add a content in the cache. 087 * @param workspace the current JCR workspace. 088 * @param site the current site. 089 * @param pageElementType the page element type. 090 * @param elementId the element id (page id or zoneItem id). 091 * @param subElementId the sub-element id (page id, in case the element id is a zoneItem id). 092 * @param renderingContext the current rendering context. 093 * @param content the actual content. 094 */ 095 public void storePageElement(String workspace, String site, String pageElementType, String elementId, String subElementId, RenderingContext renderingContext, SaxBuffer content) 096 { 097 if (isEnabled()) 098 { 099 // Hit count 100 if (getLogger().isDebugEnabled()) 101 { 102 incrementCount(_missCount, workspace, site, pageElementType); 103 } 104 105 Map<String, Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>>> workspaceCache = _cache.get(workspace); 106 if (workspaceCache == null) 107 { 108 workspaceCache = new HashMap<>(); 109 _cache.put(workspace, workspaceCache); 110 } 111 112 Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>> siteCache = workspaceCache.get(site); 113 if (siteCache == null) 114 { 115 siteCache = new HashMap<>(); 116 workspaceCache.put(site, siteCache); 117 } 118 119 Map<String, Map<String, Map<RenderingContext, SaxBuffer>>> elementCache = siteCache.get(pageElementType); 120 if (elementCache == null) 121 { 122 elementCache = new HashMap<>(); 123 siteCache.put(pageElementType, elementCache); 124 } 125 126 Map<String, Map<RenderingContext, SaxBuffer>> subElementCache = elementCache.get(elementId); 127 if (subElementCache == null) 128 { 129 subElementCache = new HashMap<>(); 130 elementCache.put(elementId, subElementCache); 131 } 132 133 Map<RenderingContext, SaxBuffer> renderingContextCache = subElementCache.get(subElementId); 134 if (renderingContextCache == null) 135 { 136 renderingContextCache = new HashMap<>(); 137 subElementCache.put(subElementId, renderingContextCache); 138 } 139 140 renderingContextCache.put(renderingContext, content); 141 } 142 } 143 144 /** 145 * Returns the data in the cache, or null if none. 146 * @param workspace the current JCR workspace. 147 * @param site the current site. 148 * @param pageElementType the element type. 149 * @param elementId the element id (page id or zoneItem id). 150 * @param renderingContext the current rendering context. 151 * @return the stored content. 152 */ 153 public SaxBuffer getPageElement(String workspace, String site, String pageElementType, String elementId, RenderingContext renderingContext) 154 { 155 return getPageElement(workspace, site, pageElementType, elementId, "", renderingContext); 156 } 157 158 /** 159 * Returns the data in the cache, or null if none. 160 * @param workspace the current JCR workspace. 161 * @param site the current site. 162 * @param pageElementType the 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 * @return the stored content. 167 */ 168 public SaxBuffer getPageElement(String workspace, String site, String pageElementType, String elementId, String subElementId, RenderingContext renderingContext) 169 { 170 if (isEnabled()) 171 { 172 Map<String, Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>>> workspaceCache = _cache.get(workspace); 173 if (workspaceCache != null) 174 { 175 Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>> siteCache = workspaceCache.get(site); 176 if (siteCache != null) 177 { 178 Map<String, Map<String, Map<RenderingContext, SaxBuffer>>> elementCache = siteCache.get(pageElementType); 179 180 if (elementCache != null) 181 { 182 Map<String, Map<RenderingContext, SaxBuffer>> subElementCache = elementCache.get(elementId); 183 184 if (subElementCache != null) 185 { 186 Map<RenderingContext, SaxBuffer> renderingContextCache = subElementCache.get(subElementId); 187 188 if (renderingContextCache != null) 189 { 190 // Cache hit 191 if (getLogger().isDebugEnabled()) 192 { 193 incrementCount(_currentHitCount, workspace, site, pageElementType); 194 incrementCount(_hitCount, workspace, site, pageElementType); 195 } 196 197 return renderingContextCache.get(renderingContext); 198 } 199 } 200 } 201 } 202 } 203 } 204 205 return null; 206 } 207 208 /** 209 * Returns all stored element types, or null if none. 210 * @param workspace the current JCR workspace. 211 * @param site the current site. 212 * @return all stored element types. 213 */ 214 public Set<String> getPageElementTypes(String workspace, String site) 215 { 216 if (isEnabled()) 217 { 218 Map<String, Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>>> workspaceCache = _cache.get(workspace); 219 if (workspaceCache != null) 220 { 221 Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>> siteCache = workspaceCache.get(site); 222 if (siteCache != null) 223 { 224 return new HashSet<>(siteCache.keySet()); 225 } 226 } 227 } 228 229 return Collections.emptySet(); 230 } 231 232 /** 233 * Returns all cached data for a given InputData, or null if none. 234 * @param workspace the current JCR workspace. 235 * @param site the current site. 236 * @param pageElementType the element type. 237 * @return the stored content. 238 */ 239 public Map<String, Map<String, Map<RenderingContext, SaxBuffer>>> getElementCache(String workspace, String site, String pageElementType) 240 { 241 if (isEnabled()) 242 { 243 Map<String, Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>>> workspaceCache = _cache.get(workspace); 244 if (workspaceCache != null) 245 { 246 Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>> siteCache = workspaceCache.get(site); 247 if (siteCache != null) 248 { 249 return siteCache.get(pageElementType); 250 } 251 } 252 } 253 254 return null; 255 } 256 257 /** 258 * Removes a single item from the cache. 259 * @param workspace the target JCR workspace or null to remove from all workspaces. 260 * @param site the target site. 261 * @param pageElementType the element type. 262 * @param elementId the element id. 263 */ 264 public void removeItem(String workspace, String site, String pageElementType, String elementId) 265 { 266 if (isEnabled()) 267 { 268 if (workspace != null) 269 { 270 Map<String, Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>>> workspaceCache = _cache.get(workspace); 271 if (workspaceCache != null) 272 { 273 Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>> siteCache = workspaceCache.get(site); 274 275 if (siteCache != null) 276 { 277 Map<String, Map<String, Map<RenderingContext, SaxBuffer>>> elementCache = siteCache.get(pageElementType); 278 279 if (elementCache != null) 280 { 281 elementCache.remove(elementId); 282 283 logFullHitCount(workspace, site, pageElementType); 284 } 285 } 286 } 287 } 288 else 289 { 290 // removes from all workspaces 291 for (String wspName : _cache.keySet()) 292 { 293 Map<String, Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>>> workspaceCache = _cache.get(wspName); 294 if (workspaceCache != null) 295 { 296 Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>> siteCache = workspaceCache.get(site); 297 298 if (siteCache != null) 299 { 300 Map<String, Map<String, Map<RenderingContext, SaxBuffer>>> elementCache = siteCache.get(pageElementType); 301 302 if (elementCache != null) 303 { 304 elementCache.remove(elementId); 305 306 logFullHitCount(wspName, site, pageElementType); 307 } 308 } 309 } 310 } 311 } 312 } 313 } 314 315 /** 316 * Removes all stored data. 317 */ 318 public void clear() 319 { 320 logAndResetHitCount(); 321 322 _cache.clear(); 323 _currentHitCount.clear(); 324 _hitCount.clear(); 325 _missCount.clear(); 326 } 327 328 /** 329 * Removes all data associated with a JCR workspace and a site. 330 * @param workspace the current JCR workspace. If null, means all workspaces. 331 * @param site the current site. 332 */ 333 public void clear(String workspace, String site) 334 { 335 logAndResetHitCount(workspace, site); 336 337 if (workspace != null) 338 { 339 Map<String, Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>>> workspaceCache = _cache.get(workspace); 340 if (workspaceCache != null) 341 { 342 workspaceCache.remove(site); 343 } 344 } 345 else 346 { 347 for (Map<String, Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>>> workspaceCache : _cache.values()) 348 { 349 workspaceCache.remove(site); 350 } 351 } 352 } 353 354 /** 355 * Removes all data associated with a JCR workspace, a site and a given element type. 356 * @param workspace the current JCR workspace. 357 * @param site the current site. 358 * @param pageElementType the element type. 359 */ 360 public void clear(String workspace, String site, String pageElementType) 361 { 362 logAndResetHitCount(workspace, site, pageElementType); 363 364 Map<String, Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>>> workspaceCache = _cache.get(workspace); 365 if (workspaceCache != null) 366 { 367 Map<String, Map<String, Map<String, Map<RenderingContext, SaxBuffer>>>> siteCache = workspaceCache.get(site); 368 369 if (siteCache != null) 370 { 371 siteCache.remove(pageElementType); 372 } 373 } 374 } 375 376 @Override 377 public void dispose() 378 { 379 clear(); 380 } 381 382 /** 383 * Test if the cache is enabled. 384 * @return true if the cache is enabled, false otherwise. 385 */ 386 protected boolean isEnabled() 387 { 388 Request request = ContextHelper.getRequest(_context); 389 Object disabledRequestStr = request.getAttribute(DISABLE_PE_CACHE_ATTRIBUTE); 390 391 boolean disabledRequest = disabledRequestStr != null && disabledRequestStr.equals("true"); 392 DEVMODE developerMode = DevMode.getDeveloperMode(request); 393 boolean disabled = DEVMODE.DEVELOPMENT.equals(developerMode) || DEVMODE.SUPER_DEVELOPPMENT.equals(developerMode); 394 395 return !disabled && !disabledRequest; 396 } 397 398 /** 399 * Add a hit count for a workspace, site and page element type. 400 * @param countMap the map to fill. 401 * @param workspace the workspace. 402 * @param siteName the site name. 403 * @param pageElementType the page element type. 404 */ 405 protected void incrementCount(Map<String, Map<String, Map<String, AtomicInteger>>> countMap, String workspace, String siteName, String pageElementType) 406 { 407 Map<String, Map<String, AtomicInteger>> workspaceCount = countMap.get(workspace); 408 if (workspaceCount == null) 409 { 410 workspaceCount = new HashMap<>(); 411 countMap.put(workspace, workspaceCount); 412 } 413 414 Map<String, AtomicInteger> siteCount = workspaceCount.get(siteName); 415 if (siteCount == null) 416 { 417 siteCount = new HashMap<>(); 418 workspaceCount.put(siteName, siteCount); 419 } 420 421 AtomicInteger count = siteCount.get(pageElementType); 422 if (count == null) 423 { 424 count = new AtomicInteger(); 425 siteCount.put(pageElementType, count); 426 } 427 428 count.incrementAndGet(); 429 } 430 431 /** 432 * Get a hit or miss count for a workspace, site and page element type. 433 * @param countMap the count map. 434 * @param workspace the workspace. 435 * @param siteName the site name. 436 * @param pageElementType the page element type. 437 * @return the count. 438 */ 439 protected int getCount(Map<String, Map<String, Map<String, AtomicInteger>>> countMap, String workspace, String siteName, String pageElementType) 440 { 441 int count = 0; 442 443 if (countMap.containsKey(workspace)) 444 { 445 Map<String, Map<String, AtomicInteger>> workspaceCount = countMap.get(workspace); 446 447 if (workspaceCount.containsKey(siteName)) 448 { 449 Map<String, AtomicInteger> siteCount = workspaceCount.get(siteName); 450 451 if (siteCount.containsKey(pageElementType)) 452 { 453 count = siteCount.get(pageElementType).get(); 454 } 455 } 456 } 457 458 return count; 459 } 460 461 /** 462 * Reset the hit count after logging the old value. 463 */ 464 protected void logAndResetHitCount() 465 { 466 for (String workspace : _currentHitCount.keySet()) 467 { 468 Map<String, Map<String, AtomicInteger>> workspaceHitCount = _currentHitCount.get(workspace); 469 470 for (String siteName : workspaceHitCount.keySet()) 471 { 472 Map<String, AtomicInteger> siteHitCount = workspaceHitCount.get(siteName); 473 474 for (String pageElementType : siteHitCount.keySet()) 475 { 476 logAndResetHitCount(workspace, siteName, pageElementType, siteHitCount.get(pageElementType)); 477 } 478 } 479 } 480 } 481 482 /** 483 * Reset the hit count after logging the old value. 484 * @param workspace the workspace. 485 * @param siteName the site name. 486 */ 487 protected void logAndResetHitCount(String workspace, String siteName) 488 { 489 Map<String, Map<String, AtomicInteger>> workspaceHitCount = _currentHitCount.get(workspace); 490 if (workspaceHitCount == null) 491 { 492 workspaceHitCount = new HashMap<>(); 493 _currentHitCount.put(workspace, workspaceHitCount); 494 } 495 496 Map<String, AtomicInteger> siteHitCount = workspaceHitCount.get(siteName); 497 if (siteHitCount == null) 498 { 499 siteHitCount = new HashMap<>(); 500 workspaceHitCount.put(siteName, siteHitCount); 501 } 502 503 for (String pageElementType : siteHitCount.keySet()) 504 { 505 logAndResetHitCount(workspace, siteName, pageElementType, siteHitCount.get(pageElementType)); 506 } 507 } 508 509 /** 510 * Reset the hit count after logging the old value. 511 * @param workspace the workspace. 512 * @param siteName the site name. 513 * @param pageElementType the page element type. 514 */ 515 protected void logAndResetHitCount(String workspace, String siteName, String pageElementType) 516 { 517 Map<String, Map<String, AtomicInteger>> workspaceHitCount = _currentHitCount.get(workspace); 518 if (workspaceHitCount == null) 519 { 520 workspaceHitCount = new HashMap<>(); 521 _currentHitCount.put(workspace, workspaceHitCount); 522 } 523 524 Map<String, AtomicInteger> siteHitCount = workspaceHitCount.get(siteName); 525 if (siteHitCount == null) 526 { 527 siteHitCount = new HashMap<>(); 528 workspaceHitCount.put(siteName, siteHitCount); 529 } 530 531 AtomicInteger hitCount = siteHitCount.get(pageElementType); 532 if (hitCount == null) 533 { 534 hitCount = new AtomicInteger(); 535 siteHitCount.put(pageElementType, hitCount); 536 } 537 538 logAndResetHitCount(workspace, siteName, pageElementType, hitCount); 539 } 540 541 /** 542 * Reset the hit count after logging the old value. 543 * @param workspace the workspace. 544 * @param siteName the site name. 545 * @param pageElementType the page element type. 546 * @param hitCount the old hit count. 547 */ 548 protected void logAndResetHitCount(String workspace, String siteName, String pageElementType, AtomicInteger hitCount) 549 { 550 if (getLogger().isDebugEnabled()) 551 { 552 StringBuilder buff = new StringBuilder(); 553 554 buff.append("Page element cache cleared for workspace '").append(workspace) 555 .append("', site '").append(siteName).append("' and type '").append(pageElementType).append("'. ") 556 .append(hitCount.get()).append(" reads had been made."); 557 558 logFullHitCount(workspace, siteName, pageElementType); 559 } 560 561 hitCount.set(0); 562 } 563 564 /** 565 * Log the full hit and miss count for a given workspace, site and page element type. 566 * @param workspace the workspace. 567 * @param siteName the site name. 568 * @param pageElementType the page element type. 569 */ 570 protected void logFullHitCount(String workspace, String siteName, String pageElementType) 571 { 572 if (getLogger().isDebugEnabled()) 573 { 574 int fullHitCount = getCount(_hitCount, workspace, siteName, pageElementType); 575 int fullMissCount = getCount(_missCount, workspace, siteName, pageElementType); 576 577 StringBuilder buff = new StringBuilder(); 578 579 buff.append("Workspace '").append(workspace).append("', site '").append(siteName).append("' and type '").append(pageElementType) 580 .append("': so far, there were ").append(fullMissCount).append(" misses and ").append(fullHitCount).append(" hits."); 581 582 getLogger().debug(buff.toString()); 583 } 584 } 585 586}