All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencms.flex.CmsFlexCacheEntry Maven / Gradle / Ivy

Go to download

OpenCms is an enterprise-ready, easy to use website content management system based on Java and XML technology. Offering a complete set of features, OpenCms helps content managers worldwide to create and maintain beautiful websites fast and efficiently.

There is a newer version: 17.0
Show newest version
/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH & Co. KG, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.flex;

import org.opencms.cache.I_CmsLruCacheObject;
import org.opencms.file.CmsResource;
import org.opencms.flex.CmsFlexBucketConfiguration.BucketSet;
import org.opencms.i18n.CmsMessageContainer;
import org.opencms.jsp.util.CmsJspStandardContextBean;
import org.opencms.main.CmsLog;
import org.opencms.monitor.CmsMemoryMonitor;
import org.opencms.monitor.I_CmsMemoryMonitorable;
import org.opencms.util.CmsCollectionsGenericWrapper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.ServletException;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.logging.Log;

/**
 * Contains the contents of a cached resource.

* * It is basically a list of pre-generated output, * include() calls to other resources (with request parameters) and http headers that this * resource requires to be set.

* * A CmsFlexCacheEntry might also describe a redirect-call, but in this case * nothing else will be cached.

* * The pre-generated output is saved in byte[] arrays. * The include() calls are saved as Strings of the included resource name, * the parameters for the calls are saved in a HashMap. * The headers are saved in a HashMap. * In case of a redirect, the redirect target is cached in a String.

* * The CmsFlexCacheEntry can also have an expire date value, which indicates the time * that his entry will become invalid and should thus be cleared from the cache.

* * @since 6.0.0 * * @see org.opencms.cache.I_CmsLruCacheObject */ public class CmsFlexCacheEntry implements I_CmsLruCacheObject, I_CmsMemoryMonitorable { /** Initial size for lists. */ public static final int INITIAL_CAPACITY_LISTS = 10; /** The log object for this class. */ private static final Log LOG = CmsLog.getLog(CmsFlexCacheEntry.class); /** the assigned bucket set for this flex entry (may be null). */ private BucketSet m_bucketSet; /** The CacheEntry's size in bytes. */ private int m_byteSize; /** Indicates if this cache entry is completed. */ private boolean m_completed; /** The "expires" date for this Flex cache entry. */ private long m_dateExpires; /** The "last modified" date for this Flex cache entry. */ private long m_dateLastModified; /** The list of items for this resource. */ private List m_elements; /** A Map of cached headers for this resource. */ private Map> m_headers; /** Pointer to the next cache entry in the LRU cache. */ private I_CmsLruCacheObject m_next; /** Pointer to the previous cache entry in the LRU cache. */ private I_CmsLruCacheObject m_previous; /** Flag which indicates whether a cached redirect is permanent. */ private boolean m_redirectPermanent; /** A redirection target (if redirection is set). */ private String m_redirectTarget; /** The key under which this cache entry is stored in the variation map. */ private String m_variationKey; /** The variation map where this cache entry is stored. */ private Map m_variationMap; /** * Constructor for class CmsFlexCacheEntry.

* * The way to use this class is to first use this empty constructor * and later add data with the various add methods. */ public CmsFlexCacheEntry() { m_elements = new ArrayList(INITIAL_CAPACITY_LISTS); m_dateExpires = CmsResource.DATE_EXPIRED_DEFAULT; m_dateLastModified = -1; // base memory footprint of this object with all referenced objects m_byteSize = 1024; setNextLruObject(null); setPreviousLruObject(null); } /** * Adds an array of bytes to this cache entry, * this will usually be the result of some kind of output - stream.

* * @param bytes the output to save in the cache */ public void add(byte[] bytes) { if (m_completed) { return; } if (m_redirectTarget == null) { // Add only if not already redirected m_elements.add(bytes); m_byteSize += CmsMemoryMonitor.getMemorySize(bytes); } } /** * Add an include - call target resource to this cache entry.

* * @param resource a name of a resource in the OpenCms VFS * @param parameters a map of parameters specific to this include call * @param attrs a map of request attributes specific to this include call */ public void add(String resource, Map parameters, Map attrs) { if (m_completed) { return; } if (m_redirectTarget == null) { // Add only if not already redirected m_elements.add(resource); m_byteSize += CmsMemoryMonitor.getMemorySize(resource); if (parameters == null) { parameters = Collections.emptyMap(); } m_elements.add(parameters); m_byteSize += CmsMemoryMonitor.getValueSize(parameters); if (attrs == null) { attrs = Collections.emptyMap(); } m_elements.add(attrs); m_byteSize += CmsMemoryMonitor.getValueSize(attrs); } } /** * Add a map of headers to this cache entry, * which are usually collected in the class CmsFlexResponse first.

* * @param headers the map of headers to add to the entry */ public void addHeaders(Map> headers) { if (m_completed) { return; } m_headers = headers; Iterator allHeaders = m_headers.keySet().iterator(); while (allHeaders.hasNext()) { m_byteSize += CmsMemoryMonitor.getMemorySize(allHeaders.next()); } } /** * @see org.opencms.cache.I_CmsLruCacheObject#addToLruCache() */ public void addToLruCache() { // do nothing here... if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEENTRY_ADDED_ENTRY_1, this)); } } /** * Completes this cache entry.

* * A completed cache entry is made "unmodifiable", * so that no further data can be added and existing data can not be changed.

* * This is to prevent the (unlikely) case that some user-written class * tries to make changes to a cache entry.

*/ public void complete() { m_completed = true; // Prevent changing of the cached lists if (m_headers != null) { m_headers = Collections.unmodifiableMap(m_headers); } if (m_elements != null) { m_elements = Collections.unmodifiableList(m_elements); } if (LOG.isDebugEnabled()) { LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXCACHEENTRY_ENTRY_COMPLETED_1, toString())); } } /** * Returns the list of data entries of this cache entry.

* * Data entries are byte arrays representing some kind of output * or Strings representing include calls to other resources.

* * @return the list of data elements of this cache entry */ public List elements() { return m_elements; } /** * Gets the bucket set for this flex cache entry (may be null).

* * @return the bucket set for this flex cache entry */ public BucketSet getBucketSet() { return m_bucketSet; } /** * Returns the expiration date of this cache entry, * this is set to the time when the entry becomes invalid.

* * @return the expiration date value for this resource */ public long getDateExpires() { return m_dateExpires; } /** * Returns the "last modified" date for this Flex cache entry.

* * @return the "last modified" date for this Flex cache entry */ public long getDateLastModified() { return m_dateLastModified; } /** * @see org.opencms.cache.I_CmsLruCacheObject#getLruCacheCosts() */ public int getLruCacheCosts() { return m_byteSize; } /** * @see org.opencms.monitor.I_CmsMemoryMonitorable#getMemorySize() */ public int getMemorySize() { return getLruCacheCosts(); } /** * @see org.opencms.cache.I_CmsLruCacheObject#getNextLruObject() */ public I_CmsLruCacheObject getNextLruObject() { return m_next; } /** * @see org.opencms.cache.I_CmsLruCacheObject#getPreviousLruObject() */ public I_CmsLruCacheObject getPreviousLruObject() { return m_previous; } /** * @see org.opencms.cache.I_CmsLruCacheObject#getValue() */ public Object getValue() { return m_elements; } /** * @see org.opencms.cache.I_CmsLruCacheObject#removeFromLruCache() */ public void removeFromLruCache() { if ((m_variationMap != null) && (m_variationKey != null)) { m_variationMap.remove(m_variationKey); } if (LOG.isDebugEnabled()) { LOG.debug( Messages.get().getBundle().key( Messages.LOG_FLEXCACHEENTRY_REMOVED_ENTRY_FOR_VARIATION_1, m_variationKey)); } } /** * Processing method for this cached entry.

* * If this method is called, it delivers the contents of * the cached entry to the given request / response. * This includes calls to all included resources.

* * @param req the request from the client * @param res the server response * * @throws CmsFlexCacheException is thrown when problems writing to the response output-stream occur * @throws ServletException might be thrown from call to RequestDispatcher.include() * @throws IOException might be thrown from call to RequestDispatcher.include() or from Response.sendRedirect() */ public void service(CmsFlexRequest req, CmsFlexResponse res) throws CmsFlexCacheException, ServletException, IOException { if (!m_completed) { return; } if (m_redirectTarget != null) { res.setOnlyBuffering(false); res.setCmsCachingRequired(false); // redirect the response, no further output required res.sendRedirect(m_redirectTarget, m_redirectPermanent); } else { // process cached headers first CmsFlexResponse.processHeaders(m_headers, res); // check if this cache entry is a "leaf" (i.e. no further includes) boolean hasNoSubElements = (m_elements.size() == 1); // write output to stream and process all included elements for (int i = 0; i < m_elements.size(); i++) { Object o = m_elements.get(i); if (o instanceof String) { // handle cached parameters i++; Map paramMap = CmsCollectionsGenericWrapper.map(m_elements.get(i)); Map oldParamMap = null; if (paramMap.size() > 0) { oldParamMap = req.getParameterMap(); req.addParameterMap(paramMap); } // handle cached attributes i++; Map attrMap = CmsCollectionsGenericWrapper.map(m_elements.get(i)); Map oldAttrMap = null; if (attrMap.size() > 0) { oldAttrMap = req.getAttributeMap(); // to avoid issues with multi threading, try to clone the attribute instances req.addAttributeMap(cloneAttributes(attrMap)); //req.addAttributeMap(attrMap); } // do the include call req.getRequestDispatcher((String)o).include(req, res); // reset parameters if necessary if (oldParamMap != null) { req.setParameterMap(oldParamMap); } // reset attributes if necessary if (oldAttrMap != null) { req.setAttributeMap(oldAttrMap); } } else { try { res.writeToOutputStream((byte[])o, hasNoSubElements); } catch (IOException e) { CmsMessageContainer message = Messages.get().container( Messages.LOG_FLEXCACHEKEY_NOT_FOUND_1, getClass().getName()); if (LOG.isDebugEnabled()) { LOG.debug(message.key()); } throw new CmsFlexCacheException(message, e); } } } } } /** * Sets the bucket set for this flex cache entry.

* * @param bucketSet the bucket set to set */ public void setBucketSet(BucketSet bucketSet) { m_bucketSet = bucketSet; } /** * Sets the expiration date of this Flex cache entry exactly to the * given time.

* * @param dateExpires the time to expire this cache entry */ public void setDateExpires(long dateExpires) { m_dateExpires = dateExpires; if (LOG.isDebugEnabled()) { long now = System.currentTimeMillis(); LOG.debug( Messages.get().getBundle().key( Messages.LOG_FLEXCACHEENTRY_SET_EXPIRATION_DATE_3, new Long(m_dateExpires), new Long(now), new Long(m_dateExpires - now))); } } /** * Sets an expiration date for this cache entry to the next timeout, * which indicates the time this entry becomes invalid.

* * The timeout parameter represents the minute - interval in which the cache entry * is to be cleared. * The interval always starts at 0.00h. * A value of 60 would indicate that this entry will reach it's expiration date at the beginning of the next * 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.

* * @param timeout the timeout value to be set */ public void setDateExpiresToNextTimeout(long timeout) { if ((timeout < 0) || !m_completed) { return; } long now = System.currentTimeMillis(); long daytime = now % 86400000; long timeoutMinutes = timeout * 60000; setDateExpires((now - (daytime % timeoutMinutes)) + timeoutMinutes); } /** * Sets the "last modified" date for this Flex cache entry with the given value.

* * @param dateLastModified the value to set for the "last modified" date */ public void setDateLastModified(long dateLastModified) { m_dateLastModified = dateLastModified; } /** * Sets the "last modified" date for this Flex cache entry by using the last passed timeout value.

* * If a cache entry uses the timeout feature, it becomes invalid every time the timeout interval * passes. Thus the "last modified" date is the time the last timeout passed.

* * @param timeout the timeout value to use to calculate the date last modified */ public void setDateLastModifiedToPreviousTimeout(long timeout) { long now = System.currentTimeMillis(); long daytime = now % 86400000; long timeoutMinutes = timeout * 60000; setDateLastModified(now - (daytime % timeoutMinutes)); } /** * @see org.opencms.cache.I_CmsLruCacheObject#setNextLruObject(org.opencms.cache.I_CmsLruCacheObject) */ public void setNextLruObject(I_CmsLruCacheObject theNextEntry) { m_next = theNextEntry; } /** * @see org.opencms.cache.I_CmsLruCacheObject#setPreviousLruObject(org.opencms.cache.I_CmsLruCacheObject) */ public void setPreviousLruObject(I_CmsLruCacheObject thePreviousEntry) { m_previous = thePreviousEntry; } /** * Set a redirect target for this cache entry.

* * Important: * When a redirect target is set, all saved data is thrown away, * and new data will not be saved in the cache entry. * This is so since with a redirect nothing will be displayed * in the browser anyway, so there is no point in saving the data.

* * @param target The redirect target (must be a valid URL). * @param permanent true if this is a permanent redirect */ public void setRedirect(String target, boolean permanent) { if (m_completed || (target == null)) { return; } m_redirectTarget = target; m_redirectPermanent = permanent; m_byteSize = 512 + CmsMemoryMonitor.getMemorySize(target); // If we have a redirect we don't need any other output or headers m_elements = null; m_headers = null; } /** * Stores a backward reference to the map and key where this cache entry is stored.

* * This is required for the FlexCache.

* * @param theVariationKey the variation key * @param theVariationMap the variation map */ public void setVariationData(String theVariationKey, Map theVariationMap) { m_variationKey = theVariationKey; m_variationMap = theVariationMap; } /** * @see java.lang.Object#toString() * * @return a basic String representation of this CmsFlexCache entry */ @Override public String toString() { String str = null; if (m_redirectTarget == null) { str = "CmsFlexCacheEntry [" + m_elements.size() + " Elements/" + getLruCacheCosts() + " bytes]\n"; Iterator i = m_elements.iterator(); int count = 0; while (i.hasNext()) { count++; Object o = i.next(); if (o instanceof String) { str += "" + count + " - \n"; } else if (o instanceof byte[]) { str += "" + count + " - \n"; } else { str += ""; } } } else { str = "CmsFlexCacheEntry [Redirect to target=" + m_redirectTarget + "]"; } return str; } /** * Clones the attribute instances if possible.

* * @param attrs the attributes * * @return a new map instance with the cloned attributes */ private Map cloneAttributes(Map attrs) { Map result = new HashMap(); for (Entry entry : attrs.entrySet()) { if (entry.getValue() instanceof CmsJspStandardContextBean) { result.put(entry.getKey(), ((CmsJspStandardContextBean)entry.getValue()).createCopy()); } else if (entry.getValue() instanceof Cloneable) { Object clone = null; try { clone = ObjectUtils.clone(entry.getValue()); } catch (Exception e) { LOG.info(e.getMessage(), e); } result.put(entry.getKey(), clone != null ? clone : entry.getValue()); } else { result.put(entry.getKey(), entry.getValue()); } } return result; } }