001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.flex; 029 030import org.opencms.cache.CmsLruCache; 031import org.opencms.cache.I_CmsLruCacheObject; 032import org.opencms.db.CmsPublishedResource; 033import org.opencms.file.CmsObject; 034import org.opencms.flex.CmsFlexBucketConfiguration.BucketSet; 035import org.opencms.loader.CmsJspLoader; 036import org.opencms.main.CmsException; 037import org.opencms.main.CmsLog; 038import org.opencms.main.I_CmsEventListener; 039import org.opencms.main.OpenCms; 040import org.opencms.security.CmsRole; 041import org.opencms.util.CmsCollectionsGenericWrapper; 042import org.opencms.util.CmsStringUtil; 043import org.opencms.util.CmsUUID; 044 045import java.util.Collection; 046import java.util.Collections; 047import java.util.HashMap; 048import java.util.HashSet; 049import java.util.Hashtable; 050import java.util.Iterator; 051import java.util.List; 052import java.util.Map; 053import java.util.Set; 054 055import org.apache.commons.collections.map.LRUMap; 056import org.apache.commons.logging.Log; 057 058import com.google.common.collect.Lists; 059 060/** 061 * This class implements the FlexCache.<p> 062 * 063 * The data structure used is a two-level hashtable. 064 * This is optimized for the structure of the keys that are used to describe the 065 * caching behaviour of the entries. 066 * The first hash-level is calculated from the resource name, i.e. the 067 * name of the resource as it is referred to in the VFS of OpenCms. 068 * The second hash-level is calculated from the cache-key of the resource, 069 * which also is a String representing the specifc variation of the cached entry.<p> 070 * 071 * A suffix [online] or [offline] is appended to te resource name 072 * to distinguish between the online and offline projects of OpenCms. 073 * Also, for support of JSP based workplace pages, a suffix [workplace] 074 * is appended. The same cached workplace pages are used both in the online and 075 * all offline projects.<p> 076 * 077 * Entries in the first level of the cache are of type CmsFlexCacheVariation, 078 * which is a sub-class of CmsFlexCache. 079 * This class is a simple data type that contains of a Map of CmsFlexCacheEntries, 080 * with variations - Strings as keys.<p> 081 * 082 * Here's a short summary of used terms: 083 * <ul> 084 * <li><b>key:</b> 085 * A combination of a resource name and a variation. 086 * The data structure used is CmsFlexCacheKey. 087 * <li><b>resource:</b> 088 * A String with the resource name and an appended [online] of [offline] suffix. 089 * <li><b>variation:</b> 090 * A String describing a variation of a cached entry in the CmsFlexCache language. 091 * <li><b>entry:</b> 092 * A CmsFlexCacheEntry data structure which is describes a cached OpenCms resource. 093 * For every entry a key is saved which contains the resource name and the variation. 094 * </ul> 095 * 096 * Cache clearing is handled using events. 097 * The cache is fully flushed if an event {@link I_CmsEventListener#EVENT_PUBLISH_PROJECT} 098 * or {@link I_CmsEventListener#EVENT_CLEAR_CACHES} is caught.<p> 099 * 100 * @since 6.0.0 101 * 102 * @see org.opencms.flex.CmsFlexCacheKey 103 * @see org.opencms.flex.CmsFlexCacheEntry 104 * @see org.opencms.cache.CmsLruCache 105 * @see org.opencms.cache.I_CmsLruCacheObject 106 */ 107public class CmsFlexCache extends Object implements I_CmsEventListener { 108 109 /** 110 * A simple data container class for the FlexCache variations.<p> 111 */ 112 public static class CmsFlexCacheVariation extends Object { 113 114 /** The key belonging to the resource. */ 115 public CmsFlexCacheKey m_key; 116 117 /** Maps variations to CmsFlexCacheEntries. */ 118 public Map<String, I_CmsLruCacheObject> m_map; 119 120 /** 121 * Generates a new instance of CmsFlexCacheVariation.<p> 122 * 123 * @param theKey The (resource) key to contruct this variation list for 124 */ 125 public CmsFlexCacheVariation(CmsFlexCacheKey theKey) { 126 127 m_key = theKey; 128 m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS); 129 } 130 } 131 132 /** 133 * Extended LRUMap that handles the variations in case a key is removed.<p> 134 */ 135 class CmsFlexKeyMap extends LRUMap { 136 137 /** Serial version UID required for safe serialization. */ 138 private static final long serialVersionUID = 6931995916013396902L; 139 140 /** 141 * Initialize the map with the given size.<p> 142 * 143 * @param maxSize the maximum number of key to cache 144 */ 145 public CmsFlexKeyMap(int maxSize) { 146 147 super(maxSize); 148 } 149 150 /** 151 * Ensures that all variations that referenced by this key are released 152 * if the key is released.<p> 153 * 154 * @param entry the entry to remove 155 * 156 * @return <code>true</code> to actually delete the entry 157 * 158 * @see LRUMap#removeLRU(LinkEntry) 159 */ 160 @Override 161 protected boolean removeLRU(LinkEntry entry) { 162 163 CmsFlexCacheVariation v = (CmsFlexCacheVariation)entry.getValue(); 164 if (v == null) { 165 return true; 166 } 167 Map<String, I_CmsLruCacheObject> m = v.m_map; 168 if ((m == null) || (m.size() == 0)) { 169 return true; 170 } 171 Collection<I_CmsLruCacheObject> entries = m.values(); 172 synchronized (m_variationCache) { 173 for (I_CmsLruCacheObject e : entries) { 174 m_variationCache.remove(e); 175 } 176 v.m_map.clear(); 177 v.m_map = null; 178 v.m_key = null; 179 } 180 return true; 181 } 182 } 183 184 /**Constant for distinguish cache action.*/ 185 public static final String CACHE_ACTION = "action"; 186 187 /** Suffix to append to online cache entries. */ 188 public static final String CACHE_OFFLINESUFFIX = " [offline]"; 189 190 /** Suffix to append to online cache entries. */ 191 public static final String CACHE_ONLINESUFFIX = " [online]"; 192 193 /** Trigger for clearcache event: Clear complete cache. */ 194 public static final int CLEAR_ALL = 0; 195 196 /** Trigger for clearcache event: Clear only entries. */ 197 public static final int CLEAR_ENTRIES = 1; 198 199 /** Trigger for clearcache event: Clear complete offine cache. */ 200 public static final int CLEAR_OFFLINE_ALL = 4; 201 202 /** Trigger for clearcache event: Clear only offline entries. */ 203 public static final int CLEAR_OFFLINE_ENTRIES = 5; 204 205 /** Trigger for clearcache event: Clear complete online cache. */ 206 public static final int CLEAR_ONLINE_ALL = 2; 207 208 /** Trigger for clearcache event: Clear only online entries. */ 209 public static final int CLEAR_ONLINE_ENTRIES = 3; 210 211 /** The configuration for the Flex cache buckets. */ 212 public static final String CONFIG_PATH = "/system/config/flexconfig.properties"; 213 214 /** Initial cache size, this should be a power of 2 because of the Java collections implementation. */ 215 public static final int INITIAL_CAPACITY_CACHE = 512; 216 217 /** Initial size for variation lists, should be a power of 2. */ 218 public static final int INITIAL_CAPACITY_VARIATIONS = 8; 219 220 /** Offline repository constant. */ 221 public static final String REPOSITORY_OFFLINE = "offline"; 222 223 /** Online repository constant. */ 224 public static final String REPOSITORY_ONLINE = "online"; 225 226 /** The log object for this class. */ 227 private static final Log LOG = CmsLog.getLog(CmsFlexCache.class); 228 229 /** The LRU cache to organize the cached entries. */ 230 protected CmsLruCache m_variationCache; 231 232 /** The Flex bucket configuration. */ 233 private CmsFlexBucketConfiguration m_bucketConfiguration; 234 235 /** Indicates if offline resources should be cached or not. */ 236 private boolean m_cacheOffline; 237 238 /** The CMS object used for VFS operations. */ 239 private CmsObject m_cmsObject; 240 241 /** Indicates if the cache is enabled or not. */ 242 private boolean m_enabled; 243 244 /** Map to store the entries for fast lookup. */ 245 private Map<String, CmsFlexCacheVariation> m_keyCache; 246 247 /** Counter for the size. */ 248 private int m_size; 249 250 /** 251 * Constructor for class CmsFlexCache.<p> 252 * 253 * The parameter "enabled" is used to control if the cache is 254 * actually on or off. Even if you don't need the cache, you still 255 * have to create an instance of it with enabled=false. 256 * This is because you need some of the FlexCache data structures 257 * for JSP inclusion buffering.<p> 258 * 259 * @param configuration the flex cache configuration 260 */ 261 public CmsFlexCache(CmsFlexCacheConfiguration configuration) { 262 263 m_enabled = configuration.isCacheEnabled(); 264 m_cacheOffline = configuration.isCacheOffline(); 265 266 long maxCacheBytes = configuration.getMaxCacheBytes(); 267 long avgCacheBytes = configuration.getAvgCacheBytes(); 268 int maxEntryBytes = configuration.getMaxEntryBytes(); 269 int maxKeys = configuration.getMaxKeys(); 270 271 m_variationCache = new CmsLruCache(maxCacheBytes, avgCacheBytes, maxEntryBytes); 272 OpenCms.getMemoryMonitor().register(getClass().getName() + ".m_entryLruCache", m_variationCache); 273 274 if (m_enabled) { 275 CmsFlexKeyMap flexKeyMap = new CmsFlexKeyMap(maxKeys); 276 m_keyCache = Collections.synchronizedMap( 277 CmsCollectionsGenericWrapper.<String, CmsFlexCacheVariation> map(flexKeyMap)); 278 OpenCms.getMemoryMonitor().register(getClass().getName() + ".m_resourceMap", flexKeyMap); 279 280 OpenCms.addCmsEventListener( 281 this, 282 new int[] { 283 I_CmsEventListener.EVENT_PUBLISH_PROJECT, 284 I_CmsEventListener.EVENT_CLEAR_CACHES, 285 I_CmsEventListener.EVENT_FLEX_PURGE_JSP_REPOSITORY, 286 I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR}); 287 } 288 289 if (LOG.isInfoEnabled()) { 290 LOG.info( 291 Messages.get().getBundle().key( 292 Messages.INIT_FLEXCACHE_CREATED_2, 293 Boolean.valueOf(m_enabled), 294 Boolean.valueOf(m_cacheOffline))); 295 } 296 } 297 298 /** 299 * Copies the key set of a map while synchronizing on the map.<p> 300 * 301 * @param map the map whose key set should be copied 302 * @return the copied key set 303 */ 304 private static <K, V> Set<K> synchronizedCopyKeys(Map<K, V> map) { 305 306 if (map == null) { 307 return new HashSet<K>(); 308 } 309 synchronized (map) { 310 return new HashSet<K>(map.keySet()); 311 } 312 } 313 314 /** 315 * Copies a map while synchronizing on it.<p> 316 * 317 * @param map the map to copy 318 * @return the copied map 319 */ 320 private static <K, V> Map<K, V> synchronizedCopyMap(Map<K, V> map) { 321 322 if (map == null) { 323 return new HashMap<K, V>(); 324 } 325 326 synchronized (map) { 327 328 return new HashMap<K, V>(map); 329 } 330 } 331 332 /** 333 * Indicates if offline project resources are cached.<p> 334 * 335 * @return true if offline projects are cached, false if not 336 */ 337 public boolean cacheOffline() { 338 339 return m_cacheOffline; 340 } 341 342 /** 343 * Implements the CmsEvent interface, 344 * the FlexCache uses the events to clear itself in case a project is published.<p> 345 * 346 * @param event CmsEvent that has occurred 347 */ 348 public void cmsEvent(org.opencms.main.CmsEvent event) { 349 350 if (!isEnabled()) { 351 return; 352 } 353 354 switch (event.getType()) { 355 case I_CmsEventListener.EVENT_PUBLISH_PROJECT: 356 if (LOG.isDebugEnabled()) { 357 LOG.debug("FlexCache: Received event PUBLISH_PROJECT"); 358 } 359 String publishIdStr = (String)(event.getData().get(I_CmsEventListener.KEY_PUBLISHID)); 360 if (!CmsUUID.isValidUUID(publishIdStr)) { 361 clear(); 362 } else { 363 try { 364 CmsUUID publishId = new CmsUUID(publishIdStr); 365 List<CmsPublishedResource> publishedResources = m_cmsObject.readPublishedResources(publishId); 366 boolean updateConfiguration = false; 367 for (CmsPublishedResource res : publishedResources) { 368 if (res.getRootPath().equals(CONFIG_PATH)) { 369 updateConfiguration = true; 370 break; 371 } 372 } 373 CmsFlexBucketConfiguration bucketConfig = m_bucketConfiguration; 374 if (updateConfiguration) { 375 LOG.info("Flex bucket configuration was updated, re-initializing configuration..."); 376 try { 377 m_bucketConfiguration = CmsFlexBucketConfiguration.loadFromVfsFile( 378 m_cmsObject, 379 CONFIG_PATH); 380 } catch (CmsException e) { 381 LOG.error(e.getLocalizedMessage(), e); 382 } 383 // Make sure no entries built for the old configuration remain in the cache 384 clear(); 385 } else if (bucketConfig != null) { 386 boolean bucketClearOk = clearBucketsForPublishList( 387 bucketConfig, 388 publishId, 389 publishedResources); 390 if (!bucketClearOk) { 391 clear(); 392 } 393 } else { 394 clear(); 395 } 396 397 } catch (CmsException e1) { 398 LOG.error(e1.getLocalizedMessage(), e1); 399 clear(); 400 } 401 } 402 break; 403 case I_CmsEventListener.EVENT_CLEAR_CACHES: 404 if (LOG.isDebugEnabled()) { 405 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_CLEAR_CACHE_0)); 406 } 407 clear(); 408 break; 409 case I_CmsEventListener.EVENT_FLEX_PURGE_JSP_REPOSITORY: 410 if (LOG.isDebugEnabled()) { 411 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_PURGE_REPOSITORY_0)); 412 } 413 purgeJspRepository(); 414 break; 415 case I_CmsEventListener.EVENT_FLEX_CACHE_CLEAR: 416 if (LOG.isDebugEnabled()) { 417 LOG.debug( 418 Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RECEIVED_EVENT_CLEAR_CACHE_PARTIALLY_0)); 419 } 420 Map<String, ?> m = event.getData(); 421 if (m == null) { 422 break; 423 } 424 Integer it = null; 425 try { 426 it = (Integer)m.get(CACHE_ACTION); 427 } catch (Exception e) { 428 // it will be null 429 } 430 if (it == null) { 431 break; 432 } 433 int i = it.intValue(); 434 switch (i) { 435 case CLEAR_ALL: 436 clear(); 437 break; 438 case CLEAR_ENTRIES: 439 clearEntries(); 440 break; 441 case CLEAR_ONLINE_ALL: 442 clearOnline(); 443 break; 444 case CLEAR_ONLINE_ENTRIES: 445 clearOnlineEntries(); 446 break; 447 case CLEAR_OFFLINE_ALL: 448 clearOffline(); 449 break; 450 case CLEAR_OFFLINE_ENTRIES: 451 clearOfflineEntries(); 452 break; 453 default: 454 // no operation 455 } 456 break; 457 default: 458 // no operation 459 } 460 } 461 462 /** 463 * Dumps keys and variations to a string buffer, for debug purposes.<p> 464 * 465 * @param buffer the buffer to which the key information should be written 466 */ 467 public void dumpKeys(StringBuffer buffer) { 468 469 synchronized (this) { 470 for (Map.Entry<String, CmsFlexCacheVariation> entry : synchronizedCopyMap(m_keyCache).entrySet()) { 471 String key = entry.getKey(); 472 CmsFlexCacheVariation variations = entry.getValue(); 473 Map<String, I_CmsLruCacheObject> variationMap = variations.m_map; 474 for (Map.Entry<String, I_CmsLruCacheObject> varEntry : variationMap.entrySet()) { 475 String varKey = varEntry.getKey(); 476 I_CmsLruCacheObject value = varEntry.getValue(); 477 buffer.append(key + " VAR " + varKey + "\n"); 478 if (value instanceof CmsFlexCacheEntry) { 479 CmsFlexCacheEntry singleCacheEntry = (CmsFlexCacheEntry)value; 480 BucketSet buckets = singleCacheEntry.getBucketSet(); 481 if (buckets != null) { 482 buffer.append("buckets = " + buckets.toString() + "\n"); 483 } 484 } 485 } 486 } 487 } 488 } 489 490 /** 491 * Returns the CmsFlexCacheKey data structure for a given 492 * key (i.e. resource name).<p> 493 * 494 * Useful if you want to show the cache key for a resources, 495 * like on the FlexCache administration page.<p> 496 * 497 * Only users with administrator permissions are allowed 498 * to perform this operation.<p> 499 * 500 * @param key the resource name for which to look up the variation for 501 * @param cms the CmsObject used for user authorization 502 * @return the CmsFlexCacheKey data structure found for the resource 503 */ 504 public CmsFlexCacheKey getCachedKey(String key, CmsObject cms) { 505 506 if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) { 507 return null; 508 } 509 Object o = m_keyCache.get(key); 510 if (o != null) { 511 return ((CmsFlexCacheVariation)o).m_key; 512 } 513 return null; 514 } 515 516 /** 517 * Returns a set of all cached resource names.<p> 518 * 519 * Useful if you want to show a list of all cached resources, 520 * like on the FlexCache administration page.<p> 521 * 522 * Only users with administrator permissions are allowed 523 * to perform this operation.<p> 524 * 525 * @param cms the CmsObject used for user authorization 526 * @return a Set of cached resource names (which are of type String) 527 */ 528 public Set<String> getCachedResources(CmsObject cms) { 529 530 if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) { 531 return null; 532 } 533 return synchronizedCopyKeys(m_keyCache); 534 } 535 536 /** 537 * Returns all variations in the cache for a given resource name. 538 * The variations are of type String.<p> 539 * 540 * Useful if you want to show a list of all cached entry - variations, 541 * like on the FlexCache administration page.<p> 542 * 543 * Only users with administrator permissions are allowed 544 * to perform this operation.<p> 545 * 546 * @param key the resource name for which to look up the variations for 547 * @param cms the CmsObject used for user authorization 548 * @return a Set of cached variations (which are of type String) 549 */ 550 public Set<String> getCachedVariations(String key, CmsObject cms) { 551 552 if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) { 553 return null; 554 } 555 Object o = m_keyCache.get(key); 556 if (o != null) { 557 return synchronizedCopyKeys(((CmsFlexCacheVariation)o).m_map); 558 } 559 return null; 560 } 561 562 /** 563 * Returns the LRU cache where the CacheEntries are cached.<p> 564 * 565 * @return the LRU cache where the CacheEntries are cached 566 */ 567 public CmsLruCache getEntryLruCache() { 568 569 return m_variationCache; 570 } 571 572 /** 573 * Initializes the flex cache.<p> 574 * 575 * @param adminCms a CMS context with admin privileges 576 */ 577 public void initializeCms(CmsObject adminCms) { 578 579 try { 580 m_cmsObject = adminCms; 581 try { 582 String path = CONFIG_PATH; 583 if (m_cmsObject.existsResource(path)) { 584 LOG.info("Flex configuration found at " + CONFIG_PATH + ", initializing..."); 585 m_bucketConfiguration = CmsFlexBucketConfiguration.loadFromVfsFile(m_cmsObject, path); 586 } 587 } catch (Exception e) { 588 LOG.error(e.getLocalizedMessage(), e); 589 } 590 } catch (Exception e) { 591 LOG.error(e.getLocalizedMessage(), e); 592 } 593 } 594 595 /** 596 * Indicates if the cache is enabled (i.e. actually 597 * caching entries) or not.<p> 598 * 599 * @return true if the cache is enabled, false if not 600 */ 601 public boolean isEnabled() { 602 603 return m_enabled; 604 } 605 606 /** 607 * Returns the total number of cached resource keys. 608 * 609 * @return the number of resource keys in the cache 610 */ 611 public int keySize() { 612 613 if (!isEnabled()) { 614 return 0; 615 } 616 return m_keyCache.size(); 617 } 618 619 /** 620 * Returns the total number of entries in the cache.<p> 621 * 622 * @return the number of entries in the cache 623 */ 624 public int size() { 625 626 return m_variationCache.size(); 627 } 628 629 /** 630 * Looks up a specific entry in the cache.<p> 631 * 632 * In case a found entry has a timeout set, it will be checked upon lookup. 633 * In case the timeout of the entry has been reached, it will be removed from 634 * the cache (and null will be returned in this case).<p> 635 * 636 * @param key The key to look for in the cache 637 * @return the entry found for the key, or null if key is not in the cache 638 */ 639 CmsFlexCacheEntry get(CmsFlexRequestKey key) { 640 641 if (!isEnabled()) { 642 // cache is disabled 643 return null; 644 } 645 Object o = m_keyCache.get(key.getResource()); 646 if (o != null) { 647 // found a matching key in the cache 648 CmsFlexCacheVariation v = (CmsFlexCacheVariation)o; 649 String variation = v.m_key.matchRequestKey(key); 650 651 if (CmsStringUtil.isEmpty(variation)) { 652 // requested resource is not cacheable 653 return null; 654 } 655 CmsFlexCacheEntry entry = (CmsFlexCacheEntry)v.m_map.get(variation); 656 if (entry == null) { 657 // no cache entry available for variation 658 return null; 659 } 660 if (entry.getDateExpires() < System.currentTimeMillis()) { 661 // cache entry avaiable but expired, remove entry 662 m_variationCache.remove(entry); 663 return null; 664 } 665 // return the found cache entry 666 return entry; 667 } else { 668 return null; 669 } 670 } 671 672 /** 673 * Returns the CmsFlexCacheKey data structure for a given resource name.<p> 674 * 675 * @param resource the resource name for which to look up the key for 676 * @return the CmsFlexCacheKey data structure found for the resource 677 */ 678 CmsFlexCacheKey getKey(String resource) { 679 680 if (!isEnabled()) { 681 return null; 682 } 683 Object o = m_keyCache.get(resource); 684 if (o != null) { 685 if (LOG.isDebugEnabled()) { 686 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_FOUND_1, resource)); 687 } 688 return ((CmsFlexCacheVariation)o).m_key; 689 } else { 690 if (LOG.isDebugEnabled()) { 691 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_NOT_FOUND_1, resource)); 692 } 693 return null; 694 } 695 } 696 697 /** 698 * Checks if the cache is empty or if at last one element is contained.<p> 699 * 700 * @return true if the cache is empty, false otherwise 701 */ 702 boolean isEmpty() { 703 704 if (!isEnabled()) { 705 return true; 706 } 707 return m_keyCache.isEmpty(); 708 } 709 710 /** 711 * This method adds new entries to the cache.<p> 712 * 713 * The key describes the conditions under which the value can be cached. 714 * Usually the key belongs to the response. 715 * The variation describes the conditions under which the 716 * entry was created. This is usually calculated from the request. 717 * If the variation is != null, the entry is cachable.<p> 718 * 719 * @param key the key for the new value entry 720 * @param entry the CmsFlexCacheEntry to store in the cache 721 * @param variation the pre-calculated variation for the entry 722 * @param requestKey the request key from which the variation was determined 723 * @return true if the value was added to the cache, false otherwise 724 */ 725 boolean put(CmsFlexCacheKey key, CmsFlexCacheEntry entry, String variation, CmsFlexRequestKey requestKey) { 726 727 if (!isEnabled()) { 728 return false; 729 } 730 if (LOG.isDebugEnabled()) { 731 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADD_ENTRY_1, key.getResource())); 732 } 733 if (variation != null) { 734 // This is a cachable result 735 key.setVariation(variation); 736 if (LOG.isDebugEnabled()) { 737 LOG.debug( 738 Messages.get().getBundle().key( 739 Messages.LOG_FLEXCACHE_ADD_ENTRY_WITH_VARIATION_2, 740 key.getResource(), 741 key.getVariation())); 742 } 743 put(key, entry); 744 if (m_bucketConfiguration != null) { 745 try { 746 List<String> paths = key.getPathsForBuckets(requestKey); 747 if (paths.size() > 0) { 748 BucketSet buckets = m_bucketConfiguration.getBucketSet(paths); 749 entry.setBucketSet(buckets); 750 } else { 751 entry.setBucketSet(null); // bucket set of null means entries will be deleted for every publish job 752 } 753 } catch (Exception e) { 754 LOG.error(e.getLocalizedMessage(), e); 755 } 756 } 757 // Note that duplicates are NOT checked, it it assumed that this is done beforehand, 758 // while checking if the entry is already in the cache or not. 759 return true; 760 } else { 761 // Result is not cachable 762 if (LOG.isDebugEnabled()) { 763 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RESOURCE_NOT_CACHEABLE_0)); 764 } 765 return false; 766 } 767 } 768 769 /** 770 * Adds a key with a new, empty variation map to the cache.<p> 771 * 772 * @param key the key to add to the cache. 773 */ 774 void putKey(CmsFlexCacheKey key) { 775 776 if (!isEnabled()) { 777 return; 778 } 779 Object o = m_keyCache.get(key.getResource()); 780 if (o == null) { 781 // No variation map for this resource yet, so create one 782 CmsFlexCacheVariation variationMap = new CmsFlexCacheVariation(key); 783 m_keyCache.put(key.getResource(), variationMap); 784 if (LOG.isDebugEnabled()) { 785 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADD_KEY_1, key.getResource())); 786 } 787 } 788 // If != null the key is already in the cache, so we just do nothing 789 } 790 791 /** 792 * Removes an entry from the cache.<p> 793 * 794 * @param key the key which describes the entry to remove from the cache 795 */ 796 void remove(CmsFlexCacheKey key) { 797 798 if (!isEnabled()) { 799 return; 800 } 801 CmsFlexCacheVariation o = m_keyCache.get(key.getResource()); 802 if (o != null) { 803 I_CmsLruCacheObject old = o.m_map.get(key.getVariation()); 804 if (old != null) { 805 getEntryLruCache().remove(old); 806 } 807 } 808 } 809 810 /** 811 * Empties the cache completely.<p> 812 */ 813 private synchronized void clear() { 814 815 if (!isEnabled()) { 816 return; 817 } 818 m_keyCache.clear(); 819 m_size = 0; 820 821 m_variationCache.clear(); 822 823 if (LOG.isInfoEnabled()) { 824 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_0)); 825 } 826 } 827 828 /** 829 * Internal method to perform cache clearance.<p> 830 * 831 * It clears "one half" of the cache, i.e. either 832 * the online or the offline parts. 833 * A parameter is used to indicate if only 834 * the entries or keys and entries are to be cleared.<p> 835 * 836 * @param suffix used to distinguish between "[Online]" and "[Offline]" entries 837 * @param entriesOnly if <code>true</code>, only entries will be cleared, otherwise 838 * the entries and the keys will be cleared 839 */ 840 private synchronized void clearAccordingToSuffix(String suffix, boolean entriesOnly) { 841 842 Set<String> keys = synchronizedCopyKeys(m_keyCache); 843 Iterator<String> i = keys.iterator(); 844 while (i.hasNext()) { 845 String s = i.next(); 846 if (s.endsWith(suffix)) { 847 CmsFlexCacheVariation v = m_keyCache.get(s); 848 if (entriesOnly) { 849 // Clear only entry 850 m_size -= v.m_map.size(); 851 Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator(); 852 while (allEntries.hasNext()) { 853 I_CmsLruCacheObject nextObject = allEntries.next(); 854 allEntries.remove(); 855 m_variationCache.remove(nextObject); 856 } 857 v.m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS); 858 } else { 859 // Clear key and entry 860 m_size -= v.m_map.size(); 861 Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator(); 862 while (allEntries.hasNext()) { 863 I_CmsLruCacheObject nextObject = allEntries.next(); 864 allEntries.remove(); 865 m_variationCache.remove(nextObject); 866 } 867 868 v.m_map = null; 869 v.m_key = null; 870 m_keyCache.remove(s); 871 } 872 } 873 } 874 if (LOG.isInfoEnabled()) { 875 LOG.info( 876 Messages.get().getBundle().key( 877 Messages.LOG_FLEXCACHE_CLEAR_HALF_2, 878 suffix, 879 Boolean.valueOf(entriesOnly))); 880 } 881 } 882 883 /** 884 * Clears the Flex cache buckets matching the given publish list.<p> 885 * 886 * @param bucketConfig the bucket configuration to be used for checking which flex cache entry should be purged 887 * @param publishId the publish id 888 * @param publishedResources the published resources 889 * 890 * @return true if the flex buckets could be cleared successfully (if this returns false, the flex cache should fall back to the old behavior, i.e. clearing everything) 891 */ 892 private boolean clearBucketsForPublishList( 893 CmsFlexBucketConfiguration bucketConfig, 894 CmsUUID publishId, 895 List<CmsPublishedResource> publishedResources) { 896 897 long startTime = System.currentTimeMillis(); 898 String p = "[" + publishId + "] "; // Prefix for log messages 899 try { 900 901 LOG.debug(p + "Trying bucket-based flex entry cleanup"); 902 if (bucketConfig.shouldClearAll(publishedResources)) { 903 LOG.info(p + "Clearing Flex cache completely based on Flex bucket configuration."); 904 return false; 905 } else { 906 long totalEntries = 0; 907 long removedEntries = 0; 908 List<String> paths = Lists.newArrayList(); 909 for (CmsPublishedResource pubRes : publishedResources) { 910 paths.add(pubRes.getRootPath()); 911 LOG.info(p + "Published resource: " + pubRes.getRootPath()); 912 } 913 BucketSet publishListBucketSet = bucketConfig.getBucketSet(paths); 914 if (LOG.isInfoEnabled()) { 915 LOG.info(p + "Flex cache buckets for publish list: " + publishListBucketSet.toString()); 916 } 917 synchronized (this) { 918 List<CmsFlexCacheEntry> entriesToDelete = Lists.newArrayList(); 919 for (Map.Entry<String, CmsFlexCacheVariation> entry : synchronizedCopyMap(m_keyCache).entrySet()) { 920 CmsFlexCacheVariation variation = entry.getValue(); 921 if (LOG.isDebugEnabled()) { 922 LOG.debug(p + "Processing entries for " + entry.getKey()); 923 } 924 entriesToDelete.clear(); 925 926 for (Map.Entry<String, I_CmsLruCacheObject> variationEntry : synchronizedCopyMap( 927 variation.m_map).entrySet()) { 928 CmsFlexCacheEntry flexEntry = (CmsFlexCacheEntry)(variationEntry.getValue()); 929 totalEntries += 1; 930 BucketSet entryBucketSet = flexEntry.getBucketSet(); 931 if (publishListBucketSet.matchForDeletion(entryBucketSet)) { 932 entriesToDelete.add(flexEntry); 933 if (LOG.isInfoEnabled()) { 934 LOG.info(p + "Match: " + variationEntry.getKey()); 935 } 936 } else { 937 if (LOG.isDebugEnabled()) { 938 LOG.debug(p + "No match: " + variationEntry.getKey()); 939 } 940 } 941 } 942 for (CmsFlexCacheEntry entryToDelete : entriesToDelete) { 943 m_variationCache.remove(entryToDelete); 944 removedEntries += 1; 945 } 946 } 947 long endTime = System.currentTimeMillis(); 948 LOG.info(p 949 + "Removed " 950 + removedEntries 951 + " of " 952 + totalEntries 953 + " Flex cache entries, took " 954 + (endTime - startTime) 955 + " milliseconds"); 956 return true; 957 } 958 } 959 } catch (Exception e) { 960 LOG.error(p + "Exception while trying to selectively purge flex cache: " + e.getLocalizedMessage(), e); 961 return false; 962 } 963 } 964 965 /** 966 * Clears all entries in the cache, online or offline.<p> 967 * 968 * The keys are not cleared.<p> 969 * 970 * Only users with administrator permissions are allowed 971 * to perform this operation.<p> 972 */ 973 private synchronized void clearEntries() { 974 975 if (!isEnabled()) { 976 return; 977 } 978 if (LOG.isInfoEnabled()) { 979 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ALL_0)); 980 } 981 // create new set to avoid ConcurrentModificationExceptions 982 Set<String> cacheKeys = synchronizedCopyKeys(m_keyCache); 983 Iterator<String> i = cacheKeys.iterator(); 984 while (i.hasNext()) { 985 CmsFlexCacheVariation v = m_keyCache.get(i.next()); 986 Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator(); 987 while (allEntries.hasNext()) { 988 I_CmsLruCacheObject nextObject = allEntries.next(); 989 allEntries.remove(); 990 m_variationCache.remove(nextObject); 991 } 992 v.m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS); 993 } 994 m_size = 0; 995 } 996 997 /** 998 * Clears all entries and all keys from offline projects in the cache.<p> 999 * 1000 * Cached resources from the online project are not touched.<p> 1001 * 1002 * Only users with administrator permissions are allowed 1003 * to perform this operation.<p> 1004 */ 1005 private void clearOffline() { 1006 1007 if (!isEnabled()) { 1008 return; 1009 } 1010 if (LOG.isInfoEnabled()) { 1011 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_KEYS_AND_ENTRIES_0)); 1012 } 1013 clearAccordingToSuffix(CACHE_OFFLINESUFFIX, false); 1014 } 1015 1016 /** 1017 * Clears all entries from offline projects in the cache.<p> 1018 * 1019 * The keys from the offline projects are not cleared. 1020 * Cached resources from the online project are not touched.<p> 1021 * 1022 * Only users with administrator permissions are allowed 1023 * to perform this operation.<p> 1024 */ 1025 private void clearOfflineEntries() { 1026 1027 if (!isEnabled()) { 1028 return; 1029 } 1030 if (LOG.isInfoEnabled()) { 1031 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_OFFLINE_ENTRIES_0)); 1032 } 1033 clearAccordingToSuffix(CACHE_OFFLINESUFFIX, true); 1034 } 1035 1036 /** 1037 * Clears all entries and all keys from the online project in the cache.<p> 1038 * 1039 * Cached resources from the offline projects are not touched.<p> 1040 * 1041 * Only users with administrator permissions are allowed 1042 * to perform this operation.<p> 1043 */ 1044 private void clearOnline() { 1045 1046 if (!isEnabled()) { 1047 return; 1048 } 1049 if (LOG.isInfoEnabled()) { 1050 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_KEYS_AND_ENTRIES_0)); 1051 } 1052 clearAccordingToSuffix(CACHE_ONLINESUFFIX, false); 1053 } 1054 1055 /** 1056 * Clears all entries from the online project in the cache.<p> 1057 * 1058 * The keys from the online project are not cleared. 1059 * Cached resources from the offline projects are not touched.<p> 1060 * 1061 * Only users with administrator permissions are allowed 1062 * to perform this operation.<p> 1063 */ 1064 private void clearOnlineEntries() { 1065 1066 if (!isEnabled()) { 1067 return; 1068 } 1069 if (LOG.isInfoEnabled()) { 1070 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_ENTRIES_0)); 1071 } 1072 clearAccordingToSuffix(CACHE_ONLINESUFFIX, true); 1073 } 1074 1075 /** 1076 * This method purges the JSP repository dirs, 1077 * i.e. it deletes all JSP files that OpenCms has written to the 1078 * real FS.<p> 1079 * 1080 * Obviously this method must be used with caution. 1081 * Purpose of this method is to allow 1082 * a complete purge of all JSP pages on a machine after 1083 * a major update of JSP templates was made.<p> 1084 */ 1085 private synchronized void purgeJspRepository() { 1086 1087 CmsJspLoader cmsJspLoader = (CmsJspLoader)OpenCms.getResourceManager().getLoader( 1088 CmsJspLoader.RESOURCE_LOADER_ID); 1089 1090 cmsJspLoader.triggerPurge(new Runnable() { 1091 1092 @SuppressWarnings("synthetic-access") 1093 public void run() { 1094 1095 clear(); 1096 } 1097 }); 1098 } 1099 1100 /** 1101 * Save a value to the cache.<p> 1102 * 1103 * @param key the key under which the value is saved 1104 * @param theCacheEntry the entry to cache 1105 */ 1106 private void put(CmsFlexCacheKey key, CmsFlexCacheEntry theCacheEntry) { 1107 1108 CmsFlexCacheVariation o = m_keyCache.get(key.getResource()); 1109 if (key.getTimeout() > 0) { 1110 theCacheEntry.setDateExpiresToNextTimeout(key.getTimeout()); 1111 } 1112 if (o != null) { 1113 // We already have a variation map for this resource 1114 Map<String, I_CmsLruCacheObject> m = o.m_map; 1115 boolean wasAdded = true; 1116 if (!m.containsKey(key.getVariation())) { 1117 wasAdded = m_variationCache.add(theCacheEntry); 1118 } else { 1119 wasAdded = m_variationCache.touch(theCacheEntry); 1120 } 1121 1122 if (wasAdded) { 1123 theCacheEntry.setVariationData(key.getVariation(), m); 1124 m.put(key.getVariation(), theCacheEntry); 1125 } 1126 } else { 1127 // No variation map for this resource yet, so create one 1128 CmsFlexCacheVariation list = new CmsFlexCacheVariation(key); 1129 1130 boolean wasAdded = m_variationCache.add(theCacheEntry); 1131 1132 if (wasAdded) { 1133 theCacheEntry.setVariationData(key.getVariation(), list.m_map); 1134 list.m_map.put(key.getVariation(), theCacheEntry); 1135 m_keyCache.put(key.getResource(), list); 1136 } 1137 } 1138 1139 if (LOG.isDebugEnabled()) { 1140 LOG.debug( 1141 Messages.get().getBundle().key( 1142 Messages.LOG_FLEXCACHE_ADDED_ENTRY_FOR_RESOURCE_WITH_VARIATION_3, 1143 new Integer(m_size), 1144 key.getResource(), 1145 key.getVariation())); 1146 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADDED_ENTRY_1, theCacheEntry.toString())); 1147 } 1148 } 1149}