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 break; 435 } 436 int i = it.intValue(); 437 switch (i) { 438 case CLEAR_ALL: 439 clear(); 440 break; 441 case CLEAR_ENTRIES: 442 clearEntries(); 443 break; 444 case CLEAR_ONLINE_ALL: 445 clearOnline(); 446 break; 447 case CLEAR_ONLINE_ENTRIES: 448 clearOnlineEntries(); 449 break; 450 case CLEAR_OFFLINE_ALL: 451 clearOffline(); 452 break; 453 case CLEAR_OFFLINE_ENTRIES: 454 clearOfflineEntries(); 455 break; 456 default: 457 // no operation 458 } 459 break; 460 default: 461 // no operation 462 } 463 } 464 465 /** 466 * Dumps keys and variations to a string buffer, for debug purposes.<p> 467 * 468 * @param buffer the buffer to which the key information should be written 469 */ 470 public void dumpKeys(StringBuffer buffer) { 471 472 synchronized (this) { 473 for (Map.Entry<String, CmsFlexCacheVariation> entry : synchronizedCopyMap(m_keyCache).entrySet()) { 474 String key = entry.getKey(); 475 CmsFlexCacheVariation variations = entry.getValue(); 476 Map<String, I_CmsLruCacheObject> variationMap = variations.m_map; 477 for (Map.Entry<String, I_CmsLruCacheObject> varEntry : variationMap.entrySet()) { 478 String varKey = varEntry.getKey(); 479 I_CmsLruCacheObject value = varEntry.getValue(); 480 buffer.append(key + " VAR " + varKey + "\n"); 481 if (value instanceof CmsFlexCacheEntry) { 482 CmsFlexCacheEntry singleCacheEntry = (CmsFlexCacheEntry)value; 483 BucketSet buckets = singleCacheEntry.getBucketSet(); 484 if (buckets != null) { 485 buffer.append("buckets = " + buckets.toString() + "\n"); 486 } 487 } 488 } 489 } 490 } 491 } 492 493 /** 494 * Returns the CmsFlexCacheKey data structure for a given 495 * key (i.e. resource name).<p> 496 * 497 * Useful if you want to show the cache key for a resources, 498 * like on the FlexCache administration page.<p> 499 * 500 * Only users with administrator permissions are allowed 501 * to perform this operation.<p> 502 * 503 * @param key the resource name for which to look up the variation for 504 * @param cms the CmsObject used for user authorization 505 * @return the CmsFlexCacheKey data structure found for the resource 506 */ 507 public CmsFlexCacheKey getCachedKey(String key, CmsObject cms) { 508 509 if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) { 510 return null; 511 } 512 Object o = m_keyCache.get(key); 513 if (o != null) { 514 return ((CmsFlexCacheVariation)o).m_key; 515 } 516 return null; 517 } 518 519 /** 520 * Returns a set of all cached resource names.<p> 521 * 522 * Useful if you want to show a list of all cached resources, 523 * like on the FlexCache administration page.<p> 524 * 525 * Only users with administrator permissions are allowed 526 * to perform this operation.<p> 527 * 528 * @param cms the CmsObject used for user authorization 529 * @return a Set of cached resource names (which are of type String) 530 */ 531 public Set<String> getCachedResources(CmsObject cms) { 532 533 if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) { 534 return null; 535 } 536 return synchronizedCopyKeys(m_keyCache); 537 } 538 539 /** 540 * Returns all variations in the cache for a given resource name. 541 * The variations are of type String.<p> 542 * 543 * Useful if you want to show a list of all cached entry - variations, 544 * like on the FlexCache administration page.<p> 545 * 546 * Only users with administrator permissions are allowed 547 * to perform this operation.<p> 548 * 549 * @param key the resource name for which to look up the variations for 550 * @param cms the CmsObject used for user authorization 551 * @return a Set of cached variations (which are of type String) 552 */ 553 public Set<String> getCachedVariations(String key, CmsObject cms) { 554 555 if (!isEnabled() || !OpenCms.getRoleManager().hasRole(cms, CmsRole.WORKPLACE_MANAGER)) { 556 return null; 557 } 558 Object o = m_keyCache.get(key); 559 if (o != null) { 560 return synchronizedCopyKeys(((CmsFlexCacheVariation)o).m_map); 561 } 562 return null; 563 } 564 565 /** 566 * Returns the LRU cache where the CacheEntries are cached.<p> 567 * 568 * @return the LRU cache where the CacheEntries are cached 569 */ 570 public CmsLruCache getEntryLruCache() { 571 572 return m_variationCache; 573 } 574 575 /** 576 * Initializes the flex cache.<p> 577 * 578 * @param adminCms a CMS context with admin privileges 579 */ 580 public void initializeCms(CmsObject adminCms) { 581 582 try { 583 m_cmsObject = adminCms; 584 try { 585 String path = CONFIG_PATH; 586 if (m_cmsObject.existsResource(path)) { 587 LOG.info("Flex configuration found at " + CONFIG_PATH + ", initializing..."); 588 m_bucketConfiguration = CmsFlexBucketConfiguration.loadFromVfsFile(m_cmsObject, path); 589 } 590 } catch (Exception e) { 591 LOG.error(e.getLocalizedMessage(), e); 592 } 593 } catch (Exception e) { 594 LOG.error(e.getLocalizedMessage(), e); 595 } 596 } 597 598 /** 599 * Indicates if the cache is enabled (i.e. actually 600 * caching entries) or not.<p> 601 * 602 * @return true if the cache is enabled, false if not 603 */ 604 public boolean isEnabled() { 605 606 return m_enabled; 607 } 608 609 /** 610 * Returns the total number of cached resource keys. 611 * 612 * @return the number of resource keys in the cache 613 */ 614 public int keySize() { 615 616 if (!isEnabled()) { 617 return 0; 618 } 619 return m_keyCache.size(); 620 } 621 622 /** 623 * Returns the total number of entries in the cache.<p> 624 * 625 * @return the number of entries in the cache 626 */ 627 public int size() { 628 629 return m_variationCache.size(); 630 } 631 632 /** 633 * Looks up a specific entry in the cache.<p> 634 * 635 * In case a found entry has a timeout set, it will be checked upon lookup. 636 * In case the timeout of the entry has been reached, it will be removed from 637 * the cache (and null will be returned in this case).<p> 638 * 639 * @param key The key to look for in the cache 640 * @return the entry found for the key, or null if key is not in the cache 641 */ 642 CmsFlexCacheEntry get(CmsFlexRequestKey key) { 643 644 if (!isEnabled()) { 645 // cache is disabled 646 return null; 647 } 648 Object o = m_keyCache.get(key.getResource()); 649 if (o != null) { 650 // found a matching key in the cache 651 CmsFlexCacheVariation v = (CmsFlexCacheVariation)o; 652 String variation = v.m_key.matchRequestKey(key); 653 654 if (CmsStringUtil.isEmpty(variation)) { 655 // requested resource is not cacheable 656 return null; 657 } 658 CmsFlexCacheEntry entry = (CmsFlexCacheEntry)v.m_map.get(variation); 659 if (entry == null) { 660 // no cache entry available for variation 661 return null; 662 } 663 if (entry.getDateExpires() < System.currentTimeMillis()) { 664 // cache entry avaiable but expired, remove entry 665 m_variationCache.remove(entry); 666 return null; 667 } 668 // return the found cache entry 669 return entry; 670 } else { 671 return null; 672 } 673 } 674 675 /** 676 * Returns the CmsFlexCacheKey data structure for a given resource name.<p> 677 * 678 * @param resource the resource name for which to look up the key for 679 * @return the CmsFlexCacheKey data structure found for the resource 680 */ 681 CmsFlexCacheKey getKey(String resource) { 682 683 if (!isEnabled()) { 684 return null; 685 } 686 Object o = m_keyCache.get(resource); 687 if (o != null) { 688 if (LOG.isDebugEnabled()) { 689 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_FOUND_1, resource)); 690 } 691 return ((CmsFlexCacheVariation)o).m_key; 692 } else { 693 if (LOG.isDebugEnabled()) { 694 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEKEY_NOT_FOUND_1, resource)); 695 } 696 return null; 697 } 698 } 699 700 /** 701 * Checks if the cache is empty or if at last one element is contained.<p> 702 * 703 * @return true if the cache is empty, false otherwise 704 */ 705 boolean isEmpty() { 706 707 if (!isEnabled()) { 708 return true; 709 } 710 return m_keyCache.isEmpty(); 711 } 712 713 /** 714 * This method adds new entries to the cache.<p> 715 * 716 * The key describes the conditions under which the value can be cached. 717 * Usually the key belongs to the response. 718 * The variation describes the conditions under which the 719 * entry was created. This is usually calculated from the request. 720 * If the variation is != null, the entry is cachable.<p> 721 * 722 * @param key the key for the new value entry 723 * @param entry the CmsFlexCacheEntry to store in the cache 724 * @param variation the pre-calculated variation for the entry 725 * @param requestKey the request key from which the variation was determined 726 * @return true if the value was added to the cache, false otherwise 727 */ 728 boolean put(CmsFlexCacheKey key, CmsFlexCacheEntry entry, String variation, CmsFlexRequestKey requestKey) { 729 730 if (!isEnabled()) { 731 return false; 732 } 733 if (LOG.isDebugEnabled()) { 734 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADD_ENTRY_1, key.getResource())); 735 } 736 if (variation != null) { 737 // This is a cachable result 738 if (LOG.isDebugEnabled()) { 739 LOG.debug( 740 Messages.get().getBundle().key( 741 Messages.LOG_FLEXCACHE_ADD_ENTRY_WITH_VARIATION_2, 742 key.getResource(), 743 variation)); 744 } 745 put(key, entry, variation); 746 if (m_bucketConfiguration != null) { 747 try { 748 List<String> paths = key.getPathsForBuckets(requestKey); 749 if (paths.size() > 0) { 750 BucketSet buckets = m_bucketConfiguration.getBucketSet(paths); 751 entry.setBucketSet(buckets); 752 } else { 753 entry.setBucketSet(null); // bucket set of null means entries will be deleted for every publish job 754 } 755 } catch (Exception e) { 756 LOG.error(e.getLocalizedMessage(), e); 757 } 758 } 759 // Note that duplicates are NOT checked, it it assumed that this is done beforehand, 760 // while checking if the entry is already in the cache or not. 761 return true; 762 } else { 763 // Result is not cachable 764 if (LOG.isDebugEnabled()) { 765 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_RESOURCE_NOT_CACHEABLE_0)); 766 } 767 return false; 768 } 769 } 770 771 /** 772 * Adds a key with a new, empty variation map to the cache.<p> 773 * 774 * @param key the key to add to the cache. 775 */ 776 void putKey(CmsFlexCacheKey key) { 777 778 if (!isEnabled()) { 779 return; 780 } 781 Object o = m_keyCache.get(key.getResource()); 782 if (o == null) { 783 // No variation map for this resource yet, so create one 784 CmsFlexCacheVariation variationMap = new CmsFlexCacheVariation(key); 785 m_keyCache.put(key.getResource(), variationMap); 786 if (LOG.isDebugEnabled()) { 787 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADD_KEY_1, key.getResource())); 788 } 789 } 790 // If != null the key is already in the cache, so we just do nothing 791 } 792 793 /** 794 * Empties the cache completely.<p> 795 */ 796 private synchronized void clear() { 797 798 if (!isEnabled()) { 799 return; 800 } 801 m_keyCache.clear(); 802 m_size = 0; 803 804 m_variationCache.clear(); 805 806 if (LOG.isInfoEnabled()) { 807 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_0)); 808 } 809 } 810 811 /** 812 * Internal method to perform cache clearance.<p> 813 * 814 * It clears "one half" of the cache, i.e. either 815 * the online or the offline parts. 816 * A parameter is used to indicate if only 817 * the entries or keys and entries are to be cleared.<p> 818 * 819 * @param suffix used to distinguish between "[Online]" and "[Offline]" entries 820 * @param entriesOnly if <code>true</code>, only entries will be cleared, otherwise 821 * the entries and the keys will be cleared 822 */ 823 private synchronized void clearAccordingToSuffix(String suffix, boolean entriesOnly) { 824 825 Set<String> keys = synchronizedCopyKeys(m_keyCache); 826 Iterator<String> i = keys.iterator(); 827 while (i.hasNext()) { 828 String s = i.next(); 829 if (s.endsWith(suffix)) { 830 CmsFlexCacheVariation v = m_keyCache.get(s); 831 if (entriesOnly) { 832 // Clear only entry 833 m_size -= v.m_map.size(); 834 Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator(); 835 while (allEntries.hasNext()) { 836 I_CmsLruCacheObject nextObject = allEntries.next(); 837 allEntries.remove(); 838 m_variationCache.remove(nextObject); 839 } 840 v.m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS); 841 } else { 842 // Clear key and entry 843 m_size -= v.m_map.size(); 844 Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator(); 845 while (allEntries.hasNext()) { 846 I_CmsLruCacheObject nextObject = allEntries.next(); 847 allEntries.remove(); 848 m_variationCache.remove(nextObject); 849 } 850 851 v.m_map = null; 852 v.m_key = null; 853 m_keyCache.remove(s); 854 } 855 } 856 } 857 if (LOG.isInfoEnabled()) { 858 LOG.info( 859 Messages.get().getBundle().key( 860 Messages.LOG_FLEXCACHE_CLEAR_HALF_2, 861 suffix, 862 Boolean.valueOf(entriesOnly))); 863 } 864 } 865 866 /** 867 * Clears the Flex cache buckets matching the given publish list.<p> 868 * 869 * @param bucketConfig the bucket configuration to be used for checking which flex cache entry should be purged 870 * @param publishId the publish id 871 * @param publishedResources the published resources 872 * 873 * @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) 874 */ 875 private boolean clearBucketsForPublishList( 876 CmsFlexBucketConfiguration bucketConfig, 877 CmsUUID publishId, 878 List<CmsPublishedResource> publishedResources) { 879 880 long startTime = System.currentTimeMillis(); 881 String p = "[" + publishId + "] "; // Prefix for log messages 882 try { 883 884 LOG.debug(p + "Trying bucket-based flex entry cleanup"); 885 if (bucketConfig.shouldClearAll(publishedResources)) { 886 LOG.info(p + "Clearing Flex cache completely based on Flex bucket configuration."); 887 return false; 888 } else { 889 long totalEntries = 0; 890 long removedEntries = 0; 891 List<String> paths = Lists.newArrayList(); 892 for (CmsPublishedResource pubRes : publishedResources) { 893 paths.add(pubRes.getRootPath()); 894 LOG.info(p + "Published resource: " + pubRes.getRootPath()); 895 } 896 BucketSet publishListBucketSet = bucketConfig.getBucketSet(paths); 897 if (LOG.isInfoEnabled()) { 898 LOG.info(p + "Flex cache buckets for publish list: " + publishListBucketSet.toString()); 899 } 900 synchronized (this) { 901 List<CmsFlexCacheEntry> entriesToDelete = Lists.newArrayList(); 902 for (Map.Entry<String, CmsFlexCacheVariation> entry : synchronizedCopyMap(m_keyCache).entrySet()) { 903 CmsFlexCacheVariation variation = entry.getValue(); 904 if (LOG.isDebugEnabled()) { 905 LOG.debug(p + "Processing entries for " + entry.getKey()); 906 } 907 entriesToDelete.clear(); 908 909 for (Map.Entry<String, I_CmsLruCacheObject> variationEntry : synchronizedCopyMap( 910 variation.m_map).entrySet()) { 911 CmsFlexCacheEntry flexEntry = (CmsFlexCacheEntry)(variationEntry.getValue()); 912 totalEntries += 1; 913 BucketSet entryBucketSet = flexEntry.getBucketSet(); 914 if (publishListBucketSet.matchForDeletion(entryBucketSet)) { 915 entriesToDelete.add(flexEntry); 916 if (LOG.isInfoEnabled()) { 917 LOG.info(p + "Match: " + variationEntry.getKey()); 918 } 919 } else { 920 if (LOG.isDebugEnabled()) { 921 LOG.debug(p + "No match: " + variationEntry.getKey()); 922 } 923 } 924 } 925 for (CmsFlexCacheEntry entryToDelete : entriesToDelete) { 926 m_variationCache.remove(entryToDelete); 927 removedEntries += 1; 928 } 929 } 930 long endTime = System.currentTimeMillis(); 931 LOG.info( 932 p 933 + "Removed " 934 + removedEntries 935 + " of " 936 + totalEntries 937 + " Flex cache entries, took " 938 + (endTime - startTime) 939 + " milliseconds"); 940 return true; 941 } 942 } 943 } catch (Exception e) { 944 LOG.error(p + "Exception while trying to selectively purge flex cache: " + e.getLocalizedMessage(), e); 945 return false; 946 } 947 } 948 949 /** 950 * Clears all entries in the cache, online or offline.<p> 951 * 952 * The keys are not cleared.<p> 953 * 954 * Only users with administrator permissions are allowed 955 * to perform this operation.<p> 956 */ 957 private synchronized void clearEntries() { 958 959 if (!isEnabled()) { 960 return; 961 } 962 if (LOG.isInfoEnabled()) { 963 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ALL_0)); 964 } 965 // create new set to avoid ConcurrentModificationExceptions 966 Set<String> cacheKeys = synchronizedCopyKeys(m_keyCache); 967 Iterator<String> i = cacheKeys.iterator(); 968 while (i.hasNext()) { 969 CmsFlexCacheVariation v = m_keyCache.get(i.next()); 970 Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator(); 971 while (allEntries.hasNext()) { 972 I_CmsLruCacheObject nextObject = allEntries.next(); 973 allEntries.remove(); 974 m_variationCache.remove(nextObject); 975 } 976 v.m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS); 977 } 978 m_size = 0; 979 } 980 981 /** 982 * Clears all entries and all keys from offline projects in the cache.<p> 983 * 984 * Cached resources from the online project are not touched.<p> 985 * 986 * Only users with administrator permissions are allowed 987 * to perform this operation.<p> 988 */ 989 private void clearOffline() { 990 991 if (!isEnabled()) { 992 return; 993 } 994 if (LOG.isInfoEnabled()) { 995 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_KEYS_AND_ENTRIES_0)); 996 } 997 clearAccordingToSuffix(CACHE_OFFLINESUFFIX, false); 998 } 999 1000 /** 1001 * Clears all entries from offline projects in the cache.<p> 1002 * 1003 * The keys from the offline projects are not cleared. 1004 * Cached resources from the online project are not touched.<p> 1005 * 1006 * Only users with administrator permissions are allowed 1007 * to perform this operation.<p> 1008 */ 1009 private void clearOfflineEntries() { 1010 1011 if (!isEnabled()) { 1012 return; 1013 } 1014 if (LOG.isInfoEnabled()) { 1015 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_OFFLINE_ENTRIES_0)); 1016 } 1017 clearAccordingToSuffix(CACHE_OFFLINESUFFIX, true); 1018 } 1019 1020 /** 1021 * Clears all entries and all keys from the online project in the cache.<p> 1022 * 1023 * Cached resources from the offline projects are not touched.<p> 1024 * 1025 * Only users with administrator permissions are allowed 1026 * to perform this operation.<p> 1027 */ 1028 private void clearOnline() { 1029 1030 if (!isEnabled()) { 1031 return; 1032 } 1033 if (LOG.isInfoEnabled()) { 1034 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_KEYS_AND_ENTRIES_0)); 1035 } 1036 clearAccordingToSuffix(CACHE_ONLINESUFFIX, false); 1037 } 1038 1039 /** 1040 * Clears all entries from the online project in the cache.<p> 1041 * 1042 * The keys from the online project are not cleared. 1043 * Cached resources from the offline projects are not touched.<p> 1044 * 1045 * Only users with administrator permissions are allowed 1046 * to perform this operation.<p> 1047 */ 1048 private void clearOnlineEntries() { 1049 1050 if (!isEnabled()) { 1051 return; 1052 } 1053 if (LOG.isInfoEnabled()) { 1054 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_ENTRIES_0)); 1055 } 1056 clearAccordingToSuffix(CACHE_ONLINESUFFIX, true); 1057 } 1058 1059 /** 1060 * This method purges the JSP repository dirs, 1061 * i.e. it deletes all JSP files that OpenCms has written to the 1062 * real FS.<p> 1063 * 1064 * Obviously this method must be used with caution. 1065 * Purpose of this method is to allow 1066 * a complete purge of all JSP pages on a machine after 1067 * a major update of JSP templates was made.<p> 1068 */ 1069 private synchronized void purgeJspRepository() { 1070 1071 CmsJspLoader cmsJspLoader = (CmsJspLoader)OpenCms.getResourceManager().getLoader( 1072 CmsJspLoader.RESOURCE_LOADER_ID); 1073 1074 cmsJspLoader.triggerPurge(new Runnable() { 1075 1076 @SuppressWarnings("synthetic-access") 1077 public void run() { 1078 1079 clear(); 1080 } 1081 }); 1082 } 1083 1084 /** 1085 * Save a value to the cache.<p> 1086 * 1087 * @param key the key under which the value is saved 1088 * @param theCacheEntry the entry to cache 1089 * @param variation the variation string 1090 */ 1091 private void put(CmsFlexCacheKey key, CmsFlexCacheEntry theCacheEntry, String variation) { 1092 1093 CmsFlexCacheVariation o = m_keyCache.get(key.getResource()); 1094 if (key.getTimeout() > 0) { 1095 theCacheEntry.setDateExpiresToNextTimeout(key.getTimeout()); 1096 } 1097 if (o != null) { 1098 // We already have a variation map for this resource 1099 Map<String, I_CmsLruCacheObject> m = o.m_map; 1100 boolean wasAdded = true; 1101 if (!m.containsKey(variation)) { 1102 wasAdded = m_variationCache.add(theCacheEntry); 1103 } else { 1104 wasAdded = m_variationCache.touch(theCacheEntry); 1105 } 1106 1107 if (wasAdded) { 1108 theCacheEntry.setVariationData(variation, m); 1109 m.put(variation, theCacheEntry); 1110 } 1111 } else { 1112 // No variation map for this resource yet, so create one 1113 CmsFlexCacheVariation list = new CmsFlexCacheVariation(key); 1114 1115 boolean wasAdded = m_variationCache.add(theCacheEntry); 1116 1117 if (wasAdded) { 1118 theCacheEntry.setVariationData(variation, list.m_map); 1119 list.m_map.put(variation, theCacheEntry); 1120 m_keyCache.put(key.getResource(), list); 1121 } 1122 } 1123 1124 if (LOG.isDebugEnabled()) { 1125 LOG.debug( 1126 Messages.get().getBundle().key( 1127 Messages.LOG_FLEXCACHE_ADDED_ENTRY_FOR_RESOURCE_WITH_VARIATION_3, 1128 new Integer(m_size), 1129 key.getResource(), 1130 variation)); 1131 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADDED_ENTRY_1, theCacheEntry.toString())); 1132 } 1133 } 1134}