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 key.setVariation(variation); 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 key.getVariation())); 745 } 746 put(key, entry); 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 * Removes an entry from the cache.<p> 796 * 797 * @param key the key which describes the entry to remove from the cache 798 */ 799 void remove(CmsFlexCacheKey key) { 800 801 if (!isEnabled()) { 802 return; 803 } 804 CmsFlexCacheVariation o = m_keyCache.get(key.getResource()); 805 if (o != null) { 806 I_CmsLruCacheObject old = o.m_map.get(key.getVariation()); 807 if (old != null) { 808 getEntryLruCache().remove(old); 809 } 810 } 811 } 812 813 /** 814 * Empties the cache completely.<p> 815 */ 816 private synchronized void clear() { 817 818 if (!isEnabled()) { 819 return; 820 } 821 m_keyCache.clear(); 822 m_size = 0; 823 824 m_variationCache.clear(); 825 826 if (LOG.isInfoEnabled()) { 827 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_0)); 828 } 829 } 830 831 /** 832 * Internal method to perform cache clearance.<p> 833 * 834 * It clears "one half" of the cache, i.e. either 835 * the online or the offline parts. 836 * A parameter is used to indicate if only 837 * the entries or keys and entries are to be cleared.<p> 838 * 839 * @param suffix used to distinguish between "[Online]" and "[Offline]" entries 840 * @param entriesOnly if <code>true</code>, only entries will be cleared, otherwise 841 * the entries and the keys will be cleared 842 */ 843 private synchronized void clearAccordingToSuffix(String suffix, boolean entriesOnly) { 844 845 Set<String> keys = synchronizedCopyKeys(m_keyCache); 846 Iterator<String> i = keys.iterator(); 847 while (i.hasNext()) { 848 String s = i.next(); 849 if (s.endsWith(suffix)) { 850 CmsFlexCacheVariation v = m_keyCache.get(s); 851 if (entriesOnly) { 852 // Clear only entry 853 m_size -= v.m_map.size(); 854 Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator(); 855 while (allEntries.hasNext()) { 856 I_CmsLruCacheObject nextObject = allEntries.next(); 857 allEntries.remove(); 858 m_variationCache.remove(nextObject); 859 } 860 v.m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS); 861 } else { 862 // Clear key and entry 863 m_size -= v.m_map.size(); 864 Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator(); 865 while (allEntries.hasNext()) { 866 I_CmsLruCacheObject nextObject = allEntries.next(); 867 allEntries.remove(); 868 m_variationCache.remove(nextObject); 869 } 870 871 v.m_map = null; 872 v.m_key = null; 873 m_keyCache.remove(s); 874 } 875 } 876 } 877 if (LOG.isInfoEnabled()) { 878 LOG.info( 879 Messages.get().getBundle().key( 880 Messages.LOG_FLEXCACHE_CLEAR_HALF_2, 881 suffix, 882 Boolean.valueOf(entriesOnly))); 883 } 884 } 885 886 /** 887 * Clears the Flex cache buckets matching the given publish list.<p> 888 * 889 * @param bucketConfig the bucket configuration to be used for checking which flex cache entry should be purged 890 * @param publishId the publish id 891 * @param publishedResources the published resources 892 * 893 * @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) 894 */ 895 private boolean clearBucketsForPublishList( 896 CmsFlexBucketConfiguration bucketConfig, 897 CmsUUID publishId, 898 List<CmsPublishedResource> publishedResources) { 899 900 long startTime = System.currentTimeMillis(); 901 String p = "[" + publishId + "] "; // Prefix for log messages 902 try { 903 904 LOG.debug(p + "Trying bucket-based flex entry cleanup"); 905 if (bucketConfig.shouldClearAll(publishedResources)) { 906 LOG.info(p + "Clearing Flex cache completely based on Flex bucket configuration."); 907 return false; 908 } else { 909 long totalEntries = 0; 910 long removedEntries = 0; 911 List<String> paths = Lists.newArrayList(); 912 for (CmsPublishedResource pubRes : publishedResources) { 913 paths.add(pubRes.getRootPath()); 914 LOG.info(p + "Published resource: " + pubRes.getRootPath()); 915 } 916 BucketSet publishListBucketSet = bucketConfig.getBucketSet(paths); 917 if (LOG.isInfoEnabled()) { 918 LOG.info(p + "Flex cache buckets for publish list: " + publishListBucketSet.toString()); 919 } 920 synchronized (this) { 921 List<CmsFlexCacheEntry> entriesToDelete = Lists.newArrayList(); 922 for (Map.Entry<String, CmsFlexCacheVariation> entry : synchronizedCopyMap(m_keyCache).entrySet()) { 923 CmsFlexCacheVariation variation = entry.getValue(); 924 if (LOG.isDebugEnabled()) { 925 LOG.debug(p + "Processing entries for " + entry.getKey()); 926 } 927 entriesToDelete.clear(); 928 929 for (Map.Entry<String, I_CmsLruCacheObject> variationEntry : synchronizedCopyMap( 930 variation.m_map).entrySet()) { 931 CmsFlexCacheEntry flexEntry = (CmsFlexCacheEntry)(variationEntry.getValue()); 932 totalEntries += 1; 933 BucketSet entryBucketSet = flexEntry.getBucketSet(); 934 if (publishListBucketSet.matchForDeletion(entryBucketSet)) { 935 entriesToDelete.add(flexEntry); 936 if (LOG.isInfoEnabled()) { 937 LOG.info(p + "Match: " + variationEntry.getKey()); 938 } 939 } else { 940 if (LOG.isDebugEnabled()) { 941 LOG.debug(p + "No match: " + variationEntry.getKey()); 942 } 943 } 944 } 945 for (CmsFlexCacheEntry entryToDelete : entriesToDelete) { 946 m_variationCache.remove(entryToDelete); 947 removedEntries += 1; 948 } 949 } 950 long endTime = System.currentTimeMillis(); 951 LOG.info( 952 p 953 + "Removed " 954 + removedEntries 955 + " of " 956 + totalEntries 957 + " Flex cache entries, took " 958 + (endTime - startTime) 959 + " milliseconds"); 960 return true; 961 } 962 } 963 } catch (Exception e) { 964 LOG.error(p + "Exception while trying to selectively purge flex cache: " + e.getLocalizedMessage(), e); 965 return false; 966 } 967 } 968 969 /** 970 * Clears all entries in the cache, online or offline.<p> 971 * 972 * The keys are not cleared.<p> 973 * 974 * Only users with administrator permissions are allowed 975 * to perform this operation.<p> 976 */ 977 private synchronized void clearEntries() { 978 979 if (!isEnabled()) { 980 return; 981 } 982 if (LOG.isInfoEnabled()) { 983 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ALL_0)); 984 } 985 // create new set to avoid ConcurrentModificationExceptions 986 Set<String> cacheKeys = synchronizedCopyKeys(m_keyCache); 987 Iterator<String> i = cacheKeys.iterator(); 988 while (i.hasNext()) { 989 CmsFlexCacheVariation v = m_keyCache.get(i.next()); 990 Iterator<I_CmsLruCacheObject> allEntries = v.m_map.values().iterator(); 991 while (allEntries.hasNext()) { 992 I_CmsLruCacheObject nextObject = allEntries.next(); 993 allEntries.remove(); 994 m_variationCache.remove(nextObject); 995 } 996 v.m_map = new Hashtable<String, I_CmsLruCacheObject>(INITIAL_CAPACITY_VARIATIONS); 997 } 998 m_size = 0; 999 } 1000 1001 /** 1002 * Clears all entries and all keys from offline projects in the cache.<p> 1003 * 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 clearOffline() { 1010 1011 if (!isEnabled()) { 1012 return; 1013 } 1014 if (LOG.isInfoEnabled()) { 1015 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_KEYS_AND_ENTRIES_0)); 1016 } 1017 clearAccordingToSuffix(CACHE_OFFLINESUFFIX, false); 1018 } 1019 1020 /** 1021 * Clears all entries from offline projects in the cache.<p> 1022 * 1023 * The keys from the offline projects are not cleared. 1024 * Cached resources from the online project are not touched.<p> 1025 * 1026 * Only users with administrator permissions are allowed 1027 * to perform this operation.<p> 1028 */ 1029 private void clearOfflineEntries() { 1030 1031 if (!isEnabled()) { 1032 return; 1033 } 1034 if (LOG.isInfoEnabled()) { 1035 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_OFFLINE_ENTRIES_0)); 1036 } 1037 clearAccordingToSuffix(CACHE_OFFLINESUFFIX, true); 1038 } 1039 1040 /** 1041 * Clears all entries and all keys from the online project in the cache.<p> 1042 * 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 clearOnline() { 1049 1050 if (!isEnabled()) { 1051 return; 1052 } 1053 if (LOG.isInfoEnabled()) { 1054 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_KEYS_AND_ENTRIES_0)); 1055 } 1056 clearAccordingToSuffix(CACHE_ONLINESUFFIX, false); 1057 } 1058 1059 /** 1060 * Clears all entries from the online project in the cache.<p> 1061 * 1062 * The keys from the online project are not cleared. 1063 * Cached resources from the offline projects are not touched.<p> 1064 * 1065 * Only users with administrator permissions are allowed 1066 * to perform this operation.<p> 1067 */ 1068 private void clearOnlineEntries() { 1069 1070 if (!isEnabled()) { 1071 return; 1072 } 1073 if (LOG.isInfoEnabled()) { 1074 LOG.info(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_CLEAR_ONLINE_ENTRIES_0)); 1075 } 1076 clearAccordingToSuffix(CACHE_ONLINESUFFIX, true); 1077 } 1078 1079 /** 1080 * This method purges the JSP repository dirs, 1081 * i.e. it deletes all JSP files that OpenCms has written to the 1082 * real FS.<p> 1083 * 1084 * Obviously this method must be used with caution. 1085 * Purpose of this method is to allow 1086 * a complete purge of all JSP pages on a machine after 1087 * a major update of JSP templates was made.<p> 1088 */ 1089 private synchronized void purgeJspRepository() { 1090 1091 CmsJspLoader cmsJspLoader = (CmsJspLoader)OpenCms.getResourceManager().getLoader( 1092 CmsJspLoader.RESOURCE_LOADER_ID); 1093 1094 cmsJspLoader.triggerPurge(new Runnable() { 1095 1096 @SuppressWarnings("synthetic-access") 1097 public void run() { 1098 1099 clear(); 1100 } 1101 }); 1102 } 1103 1104 /** 1105 * Save a value to the cache.<p> 1106 * 1107 * @param key the key under which the value is saved 1108 * @param theCacheEntry the entry to cache 1109 */ 1110 private void put(CmsFlexCacheKey key, CmsFlexCacheEntry theCacheEntry) { 1111 1112 CmsFlexCacheVariation o = m_keyCache.get(key.getResource()); 1113 if (key.getTimeout() > 0) { 1114 theCacheEntry.setDateExpiresToNextTimeout(key.getTimeout()); 1115 } 1116 if (o != null) { 1117 // We already have a variation map for this resource 1118 Map<String, I_CmsLruCacheObject> m = o.m_map; 1119 boolean wasAdded = true; 1120 if (!m.containsKey(key.getVariation())) { 1121 wasAdded = m_variationCache.add(theCacheEntry); 1122 } else { 1123 wasAdded = m_variationCache.touch(theCacheEntry); 1124 } 1125 1126 if (wasAdded) { 1127 theCacheEntry.setVariationData(key.getVariation(), m); 1128 m.put(key.getVariation(), theCacheEntry); 1129 } 1130 } else { 1131 // No variation map for this resource yet, so create one 1132 CmsFlexCacheVariation list = new CmsFlexCacheVariation(key); 1133 1134 boolean wasAdded = m_variationCache.add(theCacheEntry); 1135 1136 if (wasAdded) { 1137 theCacheEntry.setVariationData(key.getVariation(), list.m_map); 1138 list.m_map.put(key.getVariation(), theCacheEntry); 1139 m_keyCache.put(key.getResource(), list); 1140 } 1141 } 1142 1143 if (LOG.isDebugEnabled()) { 1144 LOG.debug( 1145 Messages.get().getBundle().key( 1146 Messages.LOG_FLEXCACHE_ADDED_ENTRY_FOR_RESOURCE_WITH_VARIATION_3, 1147 new Integer(m_size), 1148 key.getResource(), 1149 key.getVariation())); 1150 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHE_ADDED_ENTRY_1, theCacheEntry.toString())); 1151 } 1152 } 1153}