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