<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

// For unit tests @see TestCookieManager

package org.apache.jmeter.protocol.http.control;

import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.Date;
import java.util.Map;

import org.apache.commons.collections.map.LRUMap;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.protocol.http.util.HTTPConstants;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestListener;
import org.apache.jmeter.testelement.property.BooleanProperty;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

/**
 * Handles HTTP Caching
 */
public class CacheManager extends ConfigTestElement implements TestListener, Serializable {

    private static final long serialVersionUID = 234L;

    private static final Logger log = LoggingManager.getLoggerForClass();

    //+ JMX attributes, do not change values
    public static final String CLEAR = "clearEachIteration"; // $NON-NLS-1$
    public static final String USE_EXPIRES = "useExpires"; // $NON-NLS-1$
    public static final String MAX_SIZE = "maxSize";  // $NON-NLS-1$
    //-

    private transient InheritableThreadLocal<Map<String, CacheEntry>> threadCache;

    private transient boolean useExpires; // Cached value

    private static final int DEFAULT_MAX_SIZE = 5000;

    private static final long TWO_WEEKS_MS = 15*86400*1000;

    public CacheManager() {
        setProperty(new BooleanProperty(CLEAR, false));
        setProperty(new BooleanProperty(USE_EXPIRES, false));
        clearCache();
        useExpires = false;
    }

    /*
     * Holder for storing cache details.
     * Perhaps add original response later?
     */
    // package-protected to allow access by unit-test cases
    static class CacheEntry{
        private final String lastModified;
        private final String etag;
        private final Date expires;
        public CacheEntry(String lastModified, Date expires, String etag){
           this.lastModified = lastModified;
           this.etag = etag;
           this.expires = expires;
       }
        public String getLastModified() {
            return lastModified;
        }
        public String getEtag() {
            return etag;
        }
        @Override
        public String toString(){
            return lastModified+" "+etag;
        }
        public Date getExpires() {
            return expires;
        }
    }

    /**
     * Save the Last-Modified, Etag, and Expires headers if the result is cacheable.
     * Version for Java implementation.
     * @param conn connection
     * @param res result
     */
    public void saveDetails(URLConnection conn, SampleResult res){
        if (isCacheable(res)){
            String lastModified = conn.getHeaderField(HTTPConstants.LAST_MODIFIED);
            String expires = conn.getHeaderField(HTTPConstants.EXPIRES);
            String etag = conn.getHeaderField(HTTPConstants.ETAG);
            String url = conn.getURL().toString();
            String cacheControl = conn.getHeaderField(HTTPConstants.CACHE_CONTROL);
            String date = conn.getHeaderField(HTTPConstants.DATE);
            setCache(lastModified, cacheControl, expires, etag, url, date);
        }
    }

    /**
     * Save the Last-Modified, Etag, and Expires headers if the result is cacheable.
     * Version for Commons HttpClient implementation.
     * @param method
     * @param res result
     */
    public void saveDetails(HttpMethod method, SampleResult res) throws URIException{
        if (isCacheable(res)){
            String lastModified = getHeader(method ,HTTPConstants.LAST_MODIFIED);
            String expires = getHeader(method ,HTTPConstants.EXPIRES);
            String etag = getHeader(method ,HTTPConstants.ETAG);
            String url = method.getURI().toString();
            String cacheControl = getHeader(method, HTTPConstants.CACHE_CONTROL);
            String date = getHeader(method, HTTPConstants.DATE);
            setCache(lastModified, cacheControl, expires, etag, url, date);
        }
    }

    /**
     * Save the Last-Modified, Etag, and Expires headers if the result is cacheable.
     * Version for Apache HttpClient implementation.
     * @param method
     * @param res result
     */
    public void saveDetails(HttpResponse method, SampleResult res) {
        if (isCacheable(res)){
            method.getLastHeader(USE_EXPIRES);
            String lastModified = getHeader(method ,HTTPConstants.LAST_MODIFIED);
            String expires = getHeader(method ,HTTPConstants.EXPIRES);
            String etag = getHeader(method ,HTTPConstants.ETAG);
            String cacheControl = getHeader(method, HTTPConstants.CACHE_CONTROL);
            String date = getHeader(method, HTTPConstants.DATE);
            setCache(lastModified, cacheControl, expires, etag, res.getUrlAsString(), date); // TODO correct URL?
        }
    }

    // helper method to save the cache entry
    private void setCache(String lastModified, String cacheControl, String expires, String etag, String url, String date) {
        if (log.isDebugEnabled()){
            log.debug("setCache("
                  + lastModified + "," 
                  + cacheControl + ","
                  + expires + "," 
                  + etag + ","
                  + url + ","
                  + date
                  + ")");
        }
        Date expiresDate = null; // i.e. not using Expires
        if (useExpires) {// Check that we are processing Expires/CacheControl
            final String MAX_AGE = "max-age=";
            
            if (expires != null) {
                try {
                    expiresDate = DateUtil.parseDate(expires);
                } catch (DateParseException e) {
                    if (log.isDebugEnabled()){
                        log.debug("Unable to parse Expires: '"+expires+"' "+e);
                    }
                    expiresDate = new Date(0L); // invalid dates must be treated as expired
                }
            }
            // if no-cache is present, ensure that expiresDate remains null, which forces revalidation
            if(cacheControl != null && !cacheControl.contains("no-cache")) {    
                // the max-age directive overrides the Expires header,
                if(cacheControl.contains(MAX_AGE)) {
                    long maxAgeInSecs = Long.parseLong(
                            cacheControl.substring(cacheControl.indexOf(MAX_AGE)+MAX_AGE.length())
                                .split("[, ]")[0] // Bug 51932 - allow for optional trailing attributes
                            );
                    expiresDate=new Date(System.currentTimeMillis()+maxAgeInSecs*1000);
                }   
                // No max-age && No expires => store forever
                else if(expires==null) {
                    // No max-age
                    if(!StringUtils.isEmpty(lastModified)) {
                        try {
                            Date responseDate = DateUtil.parseDate( date );
                            Date lastModifiedAsDate = DateUtil.parseDate( lastModified );
                            // see https://developer.mozilla.org/en/HTTP_Caching_FAQ
                            // see http://www.ietf.org/rfc/rfc2616.txt#13.2.4 
                            expiresDate=new Date(System.currentTimeMillis()
                                    +Math.round((responseDate.getTime()-lastModifiedAsDate.getTime())*0.1));<commit_msg>Fix comments<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

// For unit tests @see TestCookieManager

package org.apache.jmeter.protocol.http.control;

import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.Date;
import java.util.Map;

import org.apache.commons.collections.map.LRUMap;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.protocol.http.util.HTTPConstants;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestListener;
import org.apache.jmeter.testelement.property.BooleanProperty;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

/**
 * Handles HTTP Caching
 */
public class CacheManager extends ConfigTestElement implements TestListener, Serializable {

    private static final long serialVersionUID = 234L;

    private static final Logger log = LoggingManager.getLoggerForClass();

    //+ JMX attributes, do not change values
    public static final String CLEAR = "clearEachIteration"; // $NON-NLS-1$
    public static final String USE_EXPIRES = "useExpires"; // $NON-NLS-1$
    public static final String MAX_SIZE = "maxSize";  // $NON-NLS-1$
    //-

    private transient InheritableThreadLocal<Map<String, CacheEntry>> threadCache;

    private transient boolean useExpires; // Cached value

    private static final int DEFAULT_MAX_SIZE = 5000;

    private static final long TWO_WEEKS_MS = 15*86400*1000;

    public CacheManager() {
        setProperty(new BooleanProperty(CLEAR, false));
        setProperty(new BooleanProperty(USE_EXPIRES, false));
        clearCache();
        useExpires = false;
    }

    /*
     * Holder for storing cache details.
     * Perhaps add original response later?
     */
    // package-protected to allow access by unit-test cases
    static class CacheEntry{
        private final String lastModified;
        private final String etag;
        private final Date expires;
        public CacheEntry(String lastModified, Date expires, String etag){
           this.lastModified = lastModified;
           this.etag = etag;
           this.expires = expires;
       }
        public String getLastModified() {
            return lastModified;
        }
        public String getEtag() {
            return etag;
        }
        @Override
        public String toString(){
            return lastModified+" "+etag;
        }
        public Date getExpires() {
            return expires;
        }
    }

    /**
     * Save the Last-Modified, Etag, and Expires headers if the result is cacheable.
     * Version for Java implementation.
     * @param conn connection
     * @param res result
     */
    public void saveDetails(URLConnection conn, SampleResult res){
        if (isCacheable(res)){
            String lastModified = conn.getHeaderField(HTTPConstants.LAST_MODIFIED);
            String expires = conn.getHeaderField(HTTPConstants.EXPIRES);
            String etag = conn.getHeaderField(HTTPConstants.ETAG);
            String url = conn.getURL().toString();
            String cacheControl = conn.getHeaderField(HTTPConstants.CACHE_CONTROL);
            String date = conn.getHeaderField(HTTPConstants.DATE);
            setCache(lastModified, cacheControl, expires, etag, url, date);
        }
    }

    /**
     * Save the Last-Modified, Etag, and Expires headers if the result is cacheable.
     * Version for Commons HttpClient implementation.
     * @param method
     * @param res result
     */
    public void saveDetails(HttpMethod method, SampleResult res) throws URIException{
        if (isCacheable(res)){
            String lastModified = getHeader(method ,HTTPConstants.LAST_MODIFIED);
            String expires = getHeader(method ,HTTPConstants.EXPIRES);
            String etag = getHeader(method ,HTTPConstants.ETAG);
            String url = method.getURI().toString();
            String cacheControl = getHeader(method, HTTPConstants.CACHE_CONTROL);
            String date = getHeader(method, HTTPConstants.DATE);
            setCache(lastModified, cacheControl, expires, etag, url, date);
        }
    }

    /**
     * Save the Last-Modified, Etag, and Expires headers if the result is cacheable.
     * Version for Apache HttpClient implementation.
     * @param method
     * @param res result
     */
    public void saveDetails(HttpResponse method, SampleResult res) {
        if (isCacheable(res)){
            method.getLastHeader(USE_EXPIRES);
            String lastModified = getHeader(method ,HTTPConstants.LAST_MODIFIED);
            String expires = getHeader(method ,HTTPConstants.EXPIRES);
            String etag = getHeader(method ,HTTPConstants.ETAG);
            String cacheControl = getHeader(method, HTTPConstants.CACHE_CONTROL);
            String date = getHeader(method, HTTPConstants.DATE);
            setCache(lastModified, cacheControl, expires, etag, res.getUrlAsString(), date); // TODO correct URL?
        }
    }

    // helper method to save the cache entry
    private void setCache(String lastModified, String cacheControl, String expires, String etag, String url, String date) {
        if (log.isDebugEnabled()){
            log.debug("setCache("
                  + lastModified + "," 
                  + cacheControl + ","
                  + expires + "," 
                  + etag + ","
                  + url + ","
                  + date
                  + ")");
        }
        Date expiresDate = null; // i.e. not using Expires
        if (useExpires) {// Check that we are processing Expires/CacheControl
            final String MAX_AGE = "max-age=";
            
            if (expires != null) {
                try {
                    expiresDate = DateUtil.parseDate(expires);
                } catch (DateParseException e) {
                    if (log.isDebugEnabled()){
                        log.debug("Unable to parse Expires: '"+expires+"' "+e);
                    }
                    expiresDate = new Date(0L); // invalid dates must be treated as expired
                }
            }
            // if no-cache is present, ensure that expiresDate remains null, which forces revalidation
            if(cacheControl != null && !cacheControl.contains("no-cache")) {    
                // the max-age directive overrides the Expires header,
                if(cacheControl.contains(MAX_AGE)) {
                    long maxAgeInSecs = Long.parseLong(
                            cacheControl.substring(cacheControl.indexOf(MAX_AGE)+MAX_AGE.length())
                                .split("[, ]")[0] // Bug 51932 - allow for optional trailing attributes
                            );
                    expiresDate=new Date(System.currentTimeMillis()+maxAgeInSecs*1000);

                } else if(expires==null) { // No max-age && No expires
                    if(!StringUtils.isEmpty(lastModified)) {
                        try {
                            Date responseDate = DateUtil.parseDate( date );
                            Date lastModifiedAsDate = DateUtil.parseDate( lastModified );
                            // see https://developer.mozilla.org/en/HTTP_Caching_FAQ
                            // see http://www.ietf.org/rfc/rfc2616.txt#13.2.4 
                            expiresDate=new Date(System.currentTimeMillis()
                                    +Math.round((responseDate.getTime()-lastModifiedAsDate.getTime())*0.1));
{'fraction_non_alphanumeric': 0.09018734292894677, 'fraction_numerical': 0.0052547406899703, 'mean_word_length': 2.3293401787412056, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 8, 'https://': 2, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7153042', 'n_tokens_mistral': 4615, 'n_tokens_neox': 4414, 'n_words': 1262}