001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH (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, 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.I_CmsLruCacheObject; 031import org.opencms.file.CmsResource; 032import org.opencms.i18n.CmsMessageContainer; 033import org.opencms.main.CmsLog; 034import org.opencms.monitor.CmsMemoryMonitor; 035import org.opencms.monitor.I_CmsMemoryMonitorable; 036import org.opencms.util.CmsCollectionsGenericWrapper; 037 038import java.io.IOException; 039import java.util.ArrayList; 040import java.util.Collections; 041import java.util.Iterator; 042import java.util.List; 043import java.util.Map; 044 045import javax.servlet.ServletException; 046 047import org.apache.commons.logging.Log; 048 049/** 050 * Contains the contents of a cached resource.<p> 051 * 052 * It is basically a list of pre-generated output, 053 * include() calls to other resources (with request parameters) and http headers that this 054 * resource requires to be set.<p> 055 * 056 * A CmsFlexCacheEntry might also describe a redirect-call, but in this case 057 * nothing else will be cached.<p> 058 * 059 * The pre-generated output is saved in <code>byte[]</code> arrays. 060 * The include() calls are saved as Strings of the included resource name, 061 * the parameters for the calls are saved in a HashMap. 062 * The headers are saved in a HashMap. 063 * In case of a redirect, the redirect target is cached in a String.<p> 064 * 065 * The CmsFlexCacheEntry can also have an expire date value, which indicates the time 066 * that his entry will become invalid and should thus be cleared from the cache.<p> 067 * 068 * @since 6.0.0 069 * 070 * @see org.opencms.cache.I_CmsLruCacheObject 071 */ 072public class CmsFlexCacheEntry implements I_CmsLruCacheObject, I_CmsMemoryMonitorable { 073 074 /** Initial size for lists. */ 075 public static final int INITIAL_CAPACITY_LISTS = 10; 076 077 /** The log object for this class. */ 078 private static final Log LOG = CmsLog.getLog(CmsFlexCacheEntry.class); 079 080 /** The CacheEntry's size in bytes. */ 081 private int m_byteSize; 082 083 /** Indicates if this cache entry is completed. */ 084 private boolean m_completed; 085 086 /** The "expires" date for this Flex cache entry. */ 087 private long m_dateExpires; 088 089 /** The "last modified" date for this Flex cache entry. */ 090 private long m_dateLastModified; 091 092 /** The list of items for this resource. */ 093 private List<Object> m_elements; 094 095 /** A Map of cached headers for this resource. */ 096 private Map<String, List<String>> m_headers; 097 098 /** Pointer to the next cache entry in the LRU cache. */ 099 private I_CmsLruCacheObject m_next; 100 101 /** Pointer to the previous cache entry in the LRU cache. */ 102 private I_CmsLruCacheObject m_previous; 103 104 /** A redirection target (if redirection is set). */ 105 private String m_redirectTarget; 106 107 /** The key under which this cache entry is stored in the variation map. */ 108 private String m_variationKey; 109 110 /** The variation map where this cache entry is stored. */ 111 private Map<String, I_CmsLruCacheObject> m_variationMap; 112 113 /** 114 * Constructor for class CmsFlexCacheEntry.<p> 115 * 116 * The way to use this class is to first use this empty constructor 117 * and later add data with the various add methods. 118 */ 119 public CmsFlexCacheEntry() { 120 121 m_elements = new ArrayList<Object>(INITIAL_CAPACITY_LISTS); 122 m_dateExpires = CmsResource.DATE_EXPIRED_DEFAULT; 123 m_dateLastModified = -1; 124 // base memory footprint of this object with all referenced objects 125 m_byteSize = 1024; 126 127 setNextLruObject(null); 128 setPreviousLruObject(null); 129 } 130 131 /** 132 * Adds an array of bytes to this cache entry, 133 * this will usually be the result of some kind of output - stream.<p> 134 * 135 * @param bytes the output to save in the cache 136 */ 137 public void add(byte[] bytes) { 138 139 if (m_completed) { 140 return; 141 } 142 if (m_redirectTarget == null) { 143 // Add only if not already redirected 144 m_elements.add(bytes); 145 m_byteSize += CmsMemoryMonitor.getMemorySize(bytes); 146 } 147 } 148 149 /** 150 * Add an include - call target resource to this cache entry.<p> 151 * 152 * @param resource a name of a resource in the OpenCms VFS 153 * @param parameters a map of parameters specific to this include call 154 * @param attrs a map of request attributes specific to this include call 155 */ 156 public void add(String resource, Map<String, String[]> parameters, Map<String, Object> attrs) { 157 158 if (m_completed) { 159 return; 160 } 161 if (m_redirectTarget == null) { 162 // Add only if not already redirected 163 m_elements.add(resource); 164 m_byteSize += CmsMemoryMonitor.getMemorySize(resource); 165 if (parameters == null) { 166 parameters = Collections.emptyMap(); 167 } 168 m_elements.add(parameters); 169 m_byteSize += CmsMemoryMonitor.getValueSize(parameters); 170 if (attrs == null) { 171 attrs = Collections.emptyMap(); 172 } 173 m_elements.add(attrs); 174 m_byteSize += CmsMemoryMonitor.getValueSize(attrs); 175 } 176 } 177 178 /** 179 * Add a map of headers to this cache entry, 180 * which are usually collected in the class CmsFlexResponse first.<p> 181 * 182 * @param headers the map of headers to add to the entry 183 */ 184 public void addHeaders(Map<String, List<String>> headers) { 185 186 if (m_completed) { 187 return; 188 } 189 m_headers = headers; 190 191 Iterator<String> allHeaders = m_headers.keySet().iterator(); 192 while (allHeaders.hasNext()) { 193 m_byteSize += CmsMemoryMonitor.getMemorySize(allHeaders.next()); 194 } 195 } 196 197 /** 198 * @see org.opencms.cache.I_CmsLruCacheObject#addToLruCache() 199 */ 200 public void addToLruCache() { 201 202 // do nothing here... 203 if (LOG.isDebugEnabled()) { 204 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEENTRY_ADDED_ENTRY_1, this)); 205 } 206 } 207 208 /** 209 * Completes this cache entry.<p> 210 * 211 * A completed cache entry is made "unmodifiable", 212 * so that no further data can be added and existing data can not be changed.<p> 213 * 214 * This is to prevent the (unlikely) case that some user-written class 215 * tries to make changes to a cache entry.<p> 216 */ 217 public void complete() { 218 219 m_completed = true; 220 // Prevent changing of the cached lists 221 if (m_headers != null) { 222 m_headers = Collections.unmodifiableMap(m_headers); 223 } 224 if (m_elements != null) { 225 m_elements = Collections.unmodifiableList(m_elements); 226 } 227 if (LOG.isDebugEnabled()) { 228 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEENTRY_ENTRY_COMPLETED_1, toString())); 229 } 230 } 231 232 /** 233 * Returns the list of data entries of this cache entry.<p> 234 * 235 * Data entries are byte arrays representing some kind of output 236 * or Strings representing include calls to other resources.<p> 237 * 238 * @return the list of data elements of this cache entry 239 */ 240 public List<Object> elements() { 241 242 return m_elements; 243 } 244 245 /** 246 * Returns the expiration date of this cache entry, 247 * this is set to the time when the entry becomes invalid.<p> 248 * 249 * @return the expiration date value for this resource 250 */ 251 public long getDateExpires() { 252 253 return m_dateExpires; 254 } 255 256 /** 257 * Returns the "last modified" date for this Flex cache entry.<p> 258 * 259 * @return the "last modified" date for this Flex cache entry 260 */ 261 public long getDateLastModified() { 262 263 return m_dateLastModified; 264 } 265 266 /** 267 * @see org.opencms.cache.I_CmsLruCacheObject#getLruCacheCosts() 268 */ 269 public int getLruCacheCosts() { 270 271 return m_byteSize; 272 } 273 274 /** 275 * @see org.opencms.monitor.I_CmsMemoryMonitorable#getMemorySize() 276 */ 277 public int getMemorySize() { 278 279 return getLruCacheCosts(); 280 } 281 282 /** 283 * @see org.opencms.cache.I_CmsLruCacheObject#getNextLruObject() 284 */ 285 public I_CmsLruCacheObject getNextLruObject() { 286 287 return m_next; 288 } 289 290 /** 291 * @see org.opencms.cache.I_CmsLruCacheObject#getPreviousLruObject() 292 */ 293 public I_CmsLruCacheObject getPreviousLruObject() { 294 295 return m_previous; 296 } 297 298 /** 299 * @see org.opencms.cache.I_CmsLruCacheObject#getValue() 300 */ 301 public Object getValue() { 302 303 return m_elements; 304 } 305 306 /** 307 * @see org.opencms.cache.I_CmsLruCacheObject#removeFromLruCache() 308 */ 309 public void removeFromLruCache() { 310 311 if ((m_variationMap != null) && (m_variationKey != null)) { 312 m_variationMap.remove(m_variationKey); 313 } 314 if (LOG.isDebugEnabled()) { 315 LOG.debug(Messages.get().getBundle().key( 316 Messages.LOG_FLEXCACHEENTRY_REMOVED_ENTRY_FOR_VARIATION_1, 317 m_variationKey)); 318 } 319 } 320 321 /** 322 * Processing method for this cached entry.<p> 323 * 324 * If this method is called, it delivers the contents of 325 * the cached entry to the given request / response. 326 * This includes calls to all included resources.<p> 327 * 328 * @param req the request from the client 329 * @param res the server response 330 * 331 * @throws CmsFlexCacheException is thrown when problems writing to the response output-stream occur 332 * @throws ServletException might be thrown from call to RequestDispatcher.include() 333 * @throws IOException might be thrown from call to RequestDispatcher.include() or from Response.sendRedirect() 334 */ 335 public void service(CmsFlexRequest req, CmsFlexResponse res) 336 throws CmsFlexCacheException, ServletException, IOException { 337 338 if (!m_completed) { 339 return; 340 } 341 342 if (m_redirectTarget != null) { 343 res.setOnlyBuffering(false); 344 // redirect the response, no further output required 345 res.sendRedirect(m_redirectTarget); 346 } else { 347 // process cached headers first 348 CmsFlexResponse.processHeaders(m_headers, res); 349 // check if this cache entry is a "leaf" (i.e. no further includes) 350 boolean hasNoSubElements = (m_elements.size() == 1); 351 // write output to stream and process all included elements 352 for (int i = 0; i < m_elements.size(); i++) { 353 Object o = m_elements.get(i); 354 if (o instanceof String) { 355 // handle cached parameters 356 i++; 357 Map<String, String[]> paramMap = CmsCollectionsGenericWrapper.map(m_elements.get(i)); 358 Map<String, String[]> oldParamMap = null; 359 if (paramMap.size() > 0) { 360 oldParamMap = req.getParameterMap(); 361 req.addParameterMap(paramMap); 362 } 363 // handle cached attributes 364 i++; 365 Map<String, Object> attrMap = CmsCollectionsGenericWrapper.map(m_elements.get(i)); 366 Map<String, Object> oldAttrMap = null; 367 if (attrMap.size() > 0) { 368 oldAttrMap = req.getAttributeMap(); 369 req.addAttributeMap(attrMap); 370 } 371 // do the include call 372 req.getRequestDispatcher((String)o).include(req, res); 373 // reset parameters if necessary 374 if (oldParamMap != null) { 375 req.setParameterMap(oldParamMap); 376 } 377 // reset attributes if necessary 378 if (oldAttrMap != null) { 379 req.setAttributeMap(oldAttrMap); 380 } 381 } else { 382 try { 383 res.writeToOutputStream((byte[])o, hasNoSubElements); 384 } catch (IOException e) { 385 CmsMessageContainer message = Messages.get().container( 386 Messages.LOG_FLEXCACHEKEY_NOT_FOUND_1, 387 getClass().getName()); 388 if (LOG.isDebugEnabled()) { 389 LOG.debug(message.key()); 390 } 391 392 throw new CmsFlexCacheException(message, e); 393 } 394 } 395 } 396 } 397 } 398 399 /** 400 * Sets the expiration date of this Flex cache entry exactly to the 401 * given time.<p> 402 * 403 * @param dateExpires the time to expire this cache entry 404 */ 405 public void setDateExpires(long dateExpires) { 406 407 m_dateExpires = dateExpires; 408 if (LOG.isDebugEnabled()) { 409 long now = System.currentTimeMillis(); 410 LOG.debug(Messages.get().getBundle().key( 411 Messages.LOG_FLEXCACHEENTRY_SET_EXPIRATION_DATE_3, 412 new Long(m_dateExpires), 413 new Long(now), 414 new Long(m_dateExpires - now))); 415 } 416 } 417 418 /** 419 * Sets an expiration date for this cache entry to the next timeout, 420 * which indicates the time this entry becomes invalid.<p> 421 * 422 * The timeout parameter represents the minute - interval in which the cache entry 423 * is to be cleared. 424 * The interval always starts at 0.00h. 425 * A value of 60 would indicate that this entry will reach it's expiration date at the beginning of the next 426 * full hour, a timeout of 20 would indicate that the entry is invalidated at x.00, x.20 and x.40 of every hour etc.<p> 427 * 428 * @param timeout the timeout value to be set 429 */ 430 public void setDateExpiresToNextTimeout(long timeout) { 431 432 if ((timeout < 0) || !m_completed) { 433 return; 434 } 435 436 long now = System.currentTimeMillis(); 437 long daytime = now % 86400000; 438 long timeoutMinutes = timeout * 60000; 439 setDateExpires(now - (daytime % timeoutMinutes) + timeoutMinutes); 440 } 441 442 /** 443 * Sets the "last modified" date for this Flex cache entry with the given value.<p> 444 * 445 * @param dateLastModified the value to set for the "last modified" date 446 */ 447 public void setDateLastModified(long dateLastModified) { 448 449 m_dateLastModified = dateLastModified; 450 } 451 452 /** 453 * Sets the "last modified" date for this Flex cache entry by using the last passed timeout value.<p> 454 * 455 * If a cache entry uses the timeout feature, it becomes invalid every time the timeout interval 456 * passes. Thus the "last modified" date is the time the last timeout passed.<p> 457 * 458 * @param timeout the timeout value to use to calculate the date last modified 459 */ 460 public void setDateLastModifiedToPreviousTimeout(long timeout) { 461 462 long now = System.currentTimeMillis(); 463 long daytime = now % 86400000; 464 long timeoutMinutes = timeout * 60000; 465 setDateLastModified(now - (daytime % timeoutMinutes)); 466 } 467 468 /** 469 * @see org.opencms.cache.I_CmsLruCacheObject#setNextLruObject(org.opencms.cache.I_CmsLruCacheObject) 470 */ 471 public void setNextLruObject(I_CmsLruCacheObject theNextEntry) { 472 473 m_next = theNextEntry; 474 } 475 476 /** 477 * @see org.opencms.cache.I_CmsLruCacheObject#setPreviousLruObject(org.opencms.cache.I_CmsLruCacheObject) 478 */ 479 public void setPreviousLruObject(I_CmsLruCacheObject thePreviousEntry) { 480 481 m_previous = thePreviousEntry; 482 } 483 484 /** 485 * Set a redirect target for this cache entry.<p> 486 * 487 * <b>Important:</b> 488 * When a redirect target is set, all saved data is thrown away, 489 * and new data will not be saved in the cache entry. 490 * This is so since with a redirect nothing will be displayed 491 * in the browser anyway, so there is no point in saving the data.<p> 492 * 493 * @param target The redirect target (must be a valid URL). 494 */ 495 public void setRedirect(String target) { 496 497 if (m_completed || (target == null)) { 498 return; 499 } 500 m_redirectTarget = target; 501 m_byteSize = 512 + CmsMemoryMonitor.getMemorySize(target); 502 // If we have a redirect we don't need any other output or headers 503 m_elements = null; 504 m_headers = null; 505 } 506 507 /** 508 * Stores a backward reference to the map and key where this cache entry is stored.<p> 509 * 510 * This is required for the FlexCache.<p> 511 * 512 * @param theVariationKey the variation key 513 * @param theVariationMap the variation map 514 */ 515 public void setVariationData(String theVariationKey, Map<String, I_CmsLruCacheObject> theVariationMap) { 516 517 m_variationKey = theVariationKey; 518 m_variationMap = theVariationMap; 519 } 520 521 /** 522 * @see java.lang.Object#toString() 523 * 524 * @return a basic String representation of this CmsFlexCache entry 525 */ 526 @Override 527 public String toString() { 528 529 String str = null; 530 if (m_redirectTarget == null) { 531 str = "CmsFlexCacheEntry [" + m_elements.size() + " Elements/" + getLruCacheCosts() + " bytes]\n"; 532 Iterator<Object> i = m_elements.iterator(); 533 int count = 0; 534 while (i.hasNext()) { 535 count++; 536 Object o = i.next(); 537 if (o instanceof String) { 538 str += "" + count + " - <cms:include target=" + o + ">\n"; 539 } else if (o instanceof byte[]) { 540 str += "" + count + " - <![CDATA[" + new String((byte[])o) + "]]>\n"; 541 } else { 542 str += "<!--[" + o.toString() + "]-->"; 543 } 544 } 545 } else { 546 str = "CmsFlexCacheEntry [Redirect to target=" + m_redirectTarget + "]"; 547 } 548 return str; 549 } 550}