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