org.apache.jmeter.protocol.http.sampler.HTTPSamplerBaseClassifier.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.jmeter.protocol.http.sampler.HTTPSamplerBaseClassifier.java

Source

/*
 * 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.
 */
package org.apache.jmeter.protocol.http.sampler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.Argument;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.protocol.http.control.AuthManager;
import org.apache.jmeter.protocol.http.control.CacheManager;
import org.apache.jmeter.protocol.http.control.ClassifierController;
import org.apache.jmeter.protocol.http.control.Cookie;
import org.apache.jmeter.protocol.http.control.CookieManager;
import org.apache.jmeter.protocol.http.control.HeaderManager;
import org.apache.jmeter.protocol.http.parser.HTMLParseException;
import org.apache.jmeter.protocol.http.parser.HTMLParser;
import org.apache.jmeter.protocol.http.util.ConversionUtils;
import org.apache.jmeter.protocol.http.util.EncoderCache;
import org.apache.jmeter.protocol.http.util.HTTPArgument;
import org.apache.jmeter.protocol.http.util.HTTPConstants;
import org.apache.jmeter.protocol.http.util.HTTPConstantsInterface;
import org.apache.jmeter.protocol.http.util.HTTPFileArg;
import org.apache.jmeter.protocol.http.util.HTTPFileArgs;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestIterationListener;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.testelement.ThreadListener;
import org.apache.jmeter.testelement.property.BooleanProperty;
import org.apache.jmeter.testelement.property.CollectionProperty;
import org.apache.jmeter.testelement.property.IntegerProperty;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.testelement.property.PropertyIterator;
import org.apache.jmeter.testelement.property.StringProperty;
import org.apache.jmeter.testelement.property.TestElementProperty;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.util.JsseSSLManager;
import org.apache.jmeter.util.SSLManager;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.jorphan.util.JOrphanUtils;
import org.apache.log.Logger;
import org.apache.oro.text.MalformedCachePatternException;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.Perl5Matcher;
import org.nauber.alterConfiguration.Configuration;

/**
 * Common constants and methods for HTTP samplers
 * 
 */
public abstract class HTTPSamplerBaseClassifier extends AbstractSampler
        implements TestStateListener, TestIterationListener, ThreadListener, HTTPConstantsInterface {

    private String type;

    private String program;

    private String function;

    private String goal;

    private String server;

    public String getProgram() {
        String program = getPropertyAsString("PROGRAM");
        if (program != null) {
            this.program = program;
        }
        return program;
    }

    public void setProgram(String program) {
        this.program = program;
        setProperty(new StringProperty("PROGRAM", this.program));
    }

    public String getFunction() {
        String function = getPropertyAsString("FUNCTION");
        if (function != null) {
            this.function = function;
        }
        return function;
    }

    public void setFunction(String function) {
        this.function = function;
        setProperty(new StringProperty("FUNCTION", this.function));
    }

    public String getGoal() {
        String goal = getPropertyAsString("GOAL");
        if (goal != null) {
            this.goal = goal;
        }
        return goal;
    }

    public void setGoal(String goal) {
        this.goal = goal;
        setProperty(new StringProperty("GOAL", this.goal));
    }

    public String getServer() {
        String server = getPropertyAsString("SERVER");
        if (server != null) {
            this.server = server;
        }
        return server;
    }

    public void setServer(String server) {
        this.server = server;
        setProperty(new StringProperty("SERVER", this.server));
    }

    public String getType() {
        String type = getPropertyAsString("TYPE");
        if (type != null) {
            this.type = type;
        }
        return type;
    }

    public void setType(String type) {
        this.type = type;
        setProperty(new StringProperty("TYPE", this.type));
    }

    private static final long serialVersionUID = 240L;

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

    private static final Set<String> APPLIABLE_CONFIG_CLASSES = new HashSet<String>(Arrays.asList(new String[] {
            "org.apache.jmeter.config.gui.LoginConfigGui",
            "org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui",
            "org.apache.jmeter.config.gui.SimpleConfigGui", "org.apache.jmeter.protocol.http.gui.HeaderPanel",
            "org.apache.jmeter.protocol.http.gui.AuthPanel", "org.apache.jmeter.protocol.http.gui.CacheManagerGui",
            "org.apache.jmeter.protocol.http.gui.CookiePanel" }));

    // + JMX names - do not change
    public static final String ARGUMENTS = "HTTPsampler.Arguments"; // $NON-NLS-1$

    public static final String AUTH_MANAGER = "HTTPSampler.auth_manager"; // $NON-NLS-1$

    public static final String COOKIE_MANAGER = "HTTPSampler.cookie_manager"; // $NON-NLS-1$

    public static final String CACHE_MANAGER = "HTTPSampler.cache_manager"; // $NON-NLS-1$

    public static final String HEADER_MANAGER = "HTTPSampler.header_manager"; // $NON-NLS-1$

    public static final String DOMAIN = "HTTPSampler.domain"; // $NON-NLS-1$

    public static final String PORT = "HTTPSampler.port"; // $NON-NLS-1$

    public static final String PROXYHOST = "HTTPSampler.proxyHost"; // $NON-NLS-1$

    public static final String PROXYPORT = "HTTPSampler.proxyPort"; // $NON-NLS-1$

    public static final String PROXYUSER = "HTTPSampler.proxyUser"; // $NON-NLS-1$

    public static final String PROXYPASS = "HTTPSampler.proxyPass"; // $NON-NLS-1$

    public static final String CONNECT_TIMEOUT = "HTTPSampler.connect_timeout"; // $NON-NLS-1$

    public static final String RESPONSE_TIMEOUT = "HTTPSampler.response_timeout"; // $NON-NLS-1$

    public static final String METHOD = "HTTPSampler.method"; // $NON-NLS-1$

    /**
     * This is the encoding used for the content, i.e. the charset name, not the
     * header "Content-Encoding"
     */
    public static final String CONTENT_ENCODING = "HTTPSampler.contentEncoding"; // $NON-NLS-1$

    public static final String IMPLEMENTATION = "org.apache.jmeter.protocol.http.sampler.HTTPJavaImplClassifier"; // $NON-NLS-1$

    public static final String PATH = "HTTPSampler.path"; // $NON-NLS-1$

    public static final String FOLLOW_REDIRECTS = "HTTPSampler.follow_redirects"; // $NON-NLS-1$

    public static final String AUTO_REDIRECTS = "HTTPSampler.auto_redirects"; // $NON-NLS-1$

    public static final String PROTOCOL = "HTTPSampler.protocol"; // $NON-NLS-1$

    static final String PROTOCOL_FILE = "file"; // $NON-NLS-1$

    private static final String DEFAULT_PROTOCOL = HTTPConstants.PROTOCOL_HTTP;

    public static final String URL = "HTTPSampler.URL"; // $NON-NLS-1$

    /**
     * IP source to use - does not apply to Java HTTP implementation currently
     */
    public static final String IP_SOURCE = "HTTPSampler.ipSource"; // $NON-NLS-1$

    public static final String USE_KEEPALIVE = "HTTPSampler.use_keepalive"; // $NON-NLS-1$

    public static final String DO_MULTIPART_POST = "HTTPSampler.DO_MULTIPART_POST"; // $NON-NLS-1$

    public static final String BROWSER_COMPATIBLE_MULTIPART = "HTTPSampler.BROWSER_COMPATIBLE_MULTIPART"; // $NON-NLS-1$

    public static final String CONCURRENT_DWN = "HTTPSampler.concurrentDwn"; // $NON-NLS-1$

    public static final String CONCURRENT_POOL = "HTTPSampler.concurrentPool"; // $NON-NLS-1$

    private static final String CONCURRENT_POOL_DEFAULT = "4"; // default for
    // concurrent
    // pool (do not
    // change)

    // - JMX names

    public static final boolean BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT = false; // The
    // default
    // setting
    // to
    // be
    // used
    // (i.e.
    // historic)

    private static final long KEEPALIVETIME = 0; // for Thread Pool for
    // resources but no need to
    // use a special value?

    private static final long AWAIT_TERMINATION_TIMEOUT = JMeterUtils
            .getPropDefault("httpsampler.await_termination_timeout", 60); // $NON-NLS-1$
    // //
    // default
    // value:
    // 60
    // secs

    private static final boolean IGNORE_FAILED_EMBEDDED_RESOURCES = JMeterUtils
            .getPropDefault("httpsampler.ignore_failed_embedded_resources", false); // $NON-NLS-1$ // default value: false

    public static final int CONCURRENT_POOL_SIZE = 4; // Default concurrent pool
    // size for download
    // embedded resources

    public static final String DEFAULT_METHOD = HTTPConstants.GET; // $NON-NLS-1$
    // Supported methods:
    private static final String[] METHODS = { DEFAULT_METHOD, // i.e. GET
            HTTPConstants.POST, HTTPConstants.HEAD, HTTPConstants.PUT, HTTPConstants.OPTIONS, HTTPConstants.TRACE,
            HTTPConstants.DELETE, HTTPConstants.PATCH, };

    private static final List<String> METHODLIST = Collections.unmodifiableList(Arrays.asList(METHODS));

    // @see mergeFileProperties
    // Must be private, as the file list needs special handling
    private static final String FILE_ARGS = "HTTPsampler.Files"; // $NON-NLS-1$
    // MIMETYPE is kept for backward compatibility with old test plans
    private static final String MIMETYPE = "HTTPSampler.mimetype"; // $NON-NLS-1$
    // FILE_NAME is kept for backward compatibility with old test plans
    private static final String FILE_NAME = "HTTPSampler.FILE_NAME"; // $NON-NLS-1$
    /* Shown as Parameter Name on the GUI */
    // FILE_FIELD is kept for backward compatibility with old test plans
    private static final String FILE_FIELD = "HTTPSampler.FILE_FIELD"; // $NON-NLS-1$

    public static final String CONTENT_TYPE = "HTTPSampler.CONTENT_TYPE"; // $NON-NLS-1$

    // IMAGE_PARSER now really means EMBEDDED_PARSER
    public static final String IMAGE_PARSER = "HTTPSampler.image_parser"; // $NON-NLS-1$

    // Embedded URLs must match this RE (if provided)
    public static final String EMBEDDED_URL_RE = "HTTPSampler.embedded_url_re"; // $NON-NLS-1$

    public static final String MONITOR = "HTTPSampler.monitor"; // $NON-NLS-1$

    // Store MD5 hash instead of storing response
    private static final String MD5 = "HTTPSampler.md5"; // $NON-NLS-1$

    /** A number to indicate that the port has not been set. */
    public static final int UNSPECIFIED_PORT = 0;
    public static final String UNSPECIFIED_PORT_AS_STRING = "0"; // $NON-NLS-1$
    // TODO - change to use URL version? Will this affect test plans?

    /** If the port is not present in a URL, getPort() returns -1 */
    public static final int URL_UNSPECIFIED_PORT = -1;
    public static final String URL_UNSPECIFIED_PORT_AS_STRING = "-1"; // $NON-NLS-1$

    protected static final String NON_HTTP_RESPONSE_CODE = "Non HTTP response code";

    protected static final String NON_HTTP_RESPONSE_MESSAGE = "Non HTTP response message";

    public static final String POST_BODY_RAW = "HTTPSampler.postBodyRaw"; // TODO
    // -
    // belongs
    // elsewhere

    public static final boolean POST_BODY_RAW_DEFAULT = false;

    private static final String ARG_VAL_SEP = "="; // $NON-NLS-1$

    private static final String QRY_SEP = "&"; // $NON-NLS-1$

    private static final String QRY_PFX = "?"; // $NON-NLS-1$

    protected static final int MAX_REDIRECTS = JMeterUtils.getPropDefault("httpsampler.max_redirects", 5); // $NON-NLS-1$

    protected static final int MAX_FRAME_DEPTH = JMeterUtils.getPropDefault("httpsampler.max_frame_depth", 5); // $NON-NLS-1$

    // Derive the mapping of content types to parsers
    private static final Map<String, String> parsersForType = new HashMap<String, String>();
    // Not synch, but it is not modified after creation

    private static final String RESPONSE_PARSERS = // list of parsers
            JMeterUtils.getProperty("HTTPResponse.parsers");//$NON-NLS-1$

    // Control reuse of cached SSL Context in subsequent iterations
    private static final boolean USE_CACHED_SSL_CONTEXT = JMeterUtils.getPropDefault("https.use.cached.ssl.context", //$NON-NLS-1$
            true);

    static {
        String[] parsers = JOrphanUtils.split(RESPONSE_PARSERS, " ", true);// returns
        // empty
        // array
        // for
        // null
        for (int i = 0; i < parsers.length; i++) {
            final String parser = parsers[i];
            String classname = JMeterUtils.getProperty(parser + ".className");//$NON-NLS-1$
            if (classname == null) {
                log.info("Cannot find .className property for " + parser + ", using default");
                classname = "";
            }
            String typelist = JMeterUtils.getProperty(parser + ".types");//$NON-NLS-1$
            if (typelist != null) {
                String[] types = JOrphanUtils.split(typelist, " ", true);
                for (int j = 0; j < types.length; j++) {
                    final String type = types[j];
                    log.info("Parser for " + type + " is " + classname);
                    parsersForType.put(type, classname);
                }
            } else {
                log.warn("Cannot find .types property for " + parser);
            }
        }
        if (parsers.length == 0) { // revert to previous behaviour
            parsersForType.put("text/html", ""); //$NON-NLS-1$ //$NON-NLS-2$
            log.info("No response parsers defined: text/html only will be scanned for embedded resources");
        }

        log.info("Reuse SSL session context on subsequent iterations: " + USE_CACHED_SSL_CONTEXT);
    }

    // Bug 49083
    /** Whether to remove '/pathsegment/..' from redirects; default true */
    private static final boolean REMOVESLASHDOTDOT = JMeterUtils
            .getPropDefault("httpsampler.redirect.removeslashdotdot", true);

    // //////////////////// Code ///////////////////////////

    public HTTPSamplerBaseClassifier() {
        setArguments(new Arguments());
    }

    /**
     * Determine if the file should be sent as the entire Content body, i.e.
     * without any additional wrapping.
     * 
     * @return true if specified file is to be sent as the body, i.e. there is a
     *         single file entry which has a non-empty path and an empty
     *         Parameter name.
     */
    public boolean getSendFileAsPostBody() {
        // If there is one file with no parameter name, the file will
        // be sent as post body.
        HTTPFileArg[] files = getHTTPFiles();
        return (files.length == 1) && (files[0].getPath().length() > 0) && (files[0].getParamName().length() == 0);
    }

    /**
     * Determine if none of the parameters have a name, and if that is the case,
     * it means that the parameter values should be sent as the entity body
     * 
     * @return true if none of the parameters have a name specified
     */
    public boolean getSendParameterValuesAsPostBody() {
        if (getPostBodyRaw()) {
            return true;
        } else {
            boolean noArgumentsHasName = true;
            PropertyIterator args = getArguments().iterator();
            while (args.hasNext()) {
                HTTPArgument arg = (HTTPArgument) args.next().getObjectValue();
                if (arg.getName() != null && arg.getName().length() > 0) {
                    noArgumentsHasName = false;
                    break;
                }
            }
            return noArgumentsHasName;
        }
    }

    /**
     * Determine if we should use multipart/form-data or
     * application/x-www-form-urlencoded for the post
     * 
     * @return true if multipart/form-data should be used and method is POST
     */
    public boolean getUseMultipartForPost() {
        // We use multipart if we have been told so, or files are present
        // and the files should not be send as the post body
        HTTPFileArg[] files = getHTTPFiles();
        if (HTTPConstants.POST.equals(getMethod())
                && (getDoMultipartPost() || (files.length > 0 && !getSendFileAsPostBody()))) {
            return true;
        }
        return false;
    }

    public void setProtocol(String value) {
        setProperty(PROTOCOL, value.toLowerCase(java.util.Locale.ENGLISH));
    }

    /**
     * Gets the protocol, with default.
     * 
     * @return the protocol
     */
    public String getProtocol() {
        String protocol = getPropertyAsString(PROTOCOL);
        if (protocol == null || protocol.length() == 0) {
            return DEFAULT_PROTOCOL;
        }
        return protocol;
    }

    /**
     * Sets the Path attribute of the UrlConfig object Also calls parseArguments
     * to extract and store any query arguments
     * 
     * @param path
     *            The new Path value
     */
    public void setPath(String path) {
        // We know that URL arguments should always be encoded in UTF-8
        // according to spec
        setPath(path, EncoderCache.URL_ARGUMENT_ENCODING);
    }

    /**
     * Sets the PATH property; if the request is a GET or DELETE (and the path
     * does not start with http[s]://) it also calls
     * {@link #parseArguments(String, String)} to extract and store any query
     * arguments.
     * 
     * @param path
     *            The new Path value
     * @param contentEncoding
     *            The encoding used for the querystring parameter values
     */
    public void setPath(String path, String contentEncoding) {
        boolean fullUrl = path.startsWith(HTTP_PREFIX) || path.startsWith(HTTPS_PREFIX);
        if (!fullUrl && (HTTPConstants.GET.equals(getMethod()) || HTTPConstants.DELETE.equals(getMethod()))) {
            int index = path.indexOf(QRY_PFX);
            if (index > -1) {
                setProperty(PATH, path.substring(0, index));
                // Parse the arguments in querystring, assuming specified
                // encoding for values
                parseArguments(path.substring(index + 1), contentEncoding);
            } else {
                setProperty(PATH, path);
            }
        } else {
            setProperty(PATH, path);
        }
    }

    public String getPath() {
        String p = getPropertyAsString(PATH);
        return encodeSpaces(p);
    }

    public void setFollowRedirects(boolean value) {
        setProperty(new BooleanProperty(FOLLOW_REDIRECTS, value));
    }

    public boolean getFollowRedirects() {
        return getPropertyAsBoolean(FOLLOW_REDIRECTS);
    }

    public void setAutoRedirects(boolean value) {
        setProperty(new BooleanProperty(AUTO_REDIRECTS, value));
    }

    public boolean getAutoRedirects() {
        return getPropertyAsBoolean(AUTO_REDIRECTS);
    }

    public void setMethod(String value) {
        setProperty(METHOD, value);
    }

    public String getMethod() {
        return getPropertyAsString(METHOD);
    }

    /**
     * Sets the value of the encoding to be used for the content.
     * 
     * @param charsetName
     *            the name of the encoding to be used
     */
    public void setContentEncoding(String charsetName) {
        setProperty(CONTENT_ENCODING, charsetName);
    }

    /**
     * 
     * @return the encoding of the content, i.e. its charset name
     */
    public String getContentEncoding() {
        return getPropertyAsString(CONTENT_ENCODING);
    }

    public void setUseKeepAlive(boolean value) {
        setProperty(new BooleanProperty(USE_KEEPALIVE, value));
    }

    public boolean getUseKeepAlive() {
        return getPropertyAsBoolean(USE_KEEPALIVE);
    }

    public void setDoMultipartPost(boolean value) {
        setProperty(new BooleanProperty(DO_MULTIPART_POST, value));
    }

    public boolean getDoMultipartPost() {
        return getPropertyAsBoolean(DO_MULTIPART_POST, false);
    }

    public void setDoBrowserCompatibleMultipart(boolean value) {
        setProperty(BROWSER_COMPATIBLE_MULTIPART, value, BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT);
    }

    public boolean getDoBrowserCompatibleMultipart() {
        return getPropertyAsBoolean(BROWSER_COMPATIBLE_MULTIPART, BROWSER_COMPATIBLE_MULTIPART_MODE_DEFAULT);
    }

    public void setMonitor(String value) {
        this.setProperty(MONITOR, value);
    }

    public void setMonitor(boolean truth) {
        this.setProperty(MONITOR, truth);
    }

    public String getMonitor() {
        return this.getPropertyAsString(MONITOR);
    }

    public boolean isMonitor() {
        return this.getPropertyAsBoolean(MONITOR);
    }

    public void setImplementation(String value) {
        this.setProperty(IMPLEMENTATION, value);
    }

    public String getImplementation() {
        return this.getPropertyAsString(IMPLEMENTATION);
    }

    public boolean useMD5() {
        return this.getPropertyAsBoolean(MD5, false);
    }

    public void setMD5(boolean truth) {
        this.setProperty(MD5, truth, false);
    }

    /**
     * Add an argument which has already been encoded
     */
    public void addEncodedArgument(String name, String value) {
        this.addEncodedArgument(name, value, ARG_VAL_SEP);
    }

    /**
     * Creates an HTTPArgument and adds it to the current set
     * {@link #getArguments()} of arguments.
     * 
     * @param name
     *            - the parameter name
     * @param value
     *            - the parameter value
     * @param metaData
     *            - normally just '='
     * @param contentEncoding
     *            - the encoding, may be null
     */
    public void addEncodedArgument(String name, String value, String metaData, String contentEncoding) {
        if (log.isDebugEnabled()) {
            log.debug("adding argument: name: " + name + " value: " + value + " metaData: " + metaData
                    + " contentEncoding: " + contentEncoding);
        }

        HTTPArgument arg = null;
        final boolean nonEmptyEncoding = !StringUtils.isEmpty(contentEncoding);
        if (nonEmptyEncoding) {
            arg = new HTTPArgument(name, value, metaData, true, contentEncoding);
        } else {
            arg = new HTTPArgument(name, value, metaData, true);
        }

        // Check if there are any difference between name and value and their
        // encoded name and value
        String valueEncoded = null;
        if (nonEmptyEncoding) {
            try {
                valueEncoded = arg.getEncodedValue(contentEncoding);
            } catch (UnsupportedEncodingException e) {
                log.warn("Unable to get encoded value using encoding " + contentEncoding);
                valueEncoded = arg.getEncodedValue();
            }
        } else {
            valueEncoded = arg.getEncodedValue();
        }
        // If there is no difference, we mark it as not needing encoding
        if (arg.getName().equals(arg.getEncodedName()) && arg.getValue().equals(valueEncoded)) {
            arg.setAlwaysEncoded(false);
        }
        this.getArguments().addArgument(arg);
    }

    public void addEncodedArgument(String name, String value, String metaData) {
        this.addEncodedArgument(name, value, metaData, null);
    }

    public void addNonEncodedArgument(String name, String value, String metadata) {
        HTTPArgument arg = new HTTPArgument(name, value, metadata, false);
        arg.setAlwaysEncoded(false);
        this.getArguments().addArgument(arg);
    }

    public void addArgument(String name, String value) {
        this.getArguments().addArgument(new HTTPArgument(name, value));
    }

    public void addArgument(String name, String value, String metadata) {
        this.getArguments().addArgument(new HTTPArgument(name, value, metadata));
    }

    public boolean hasArguments() {
        return getArguments().getArgumentCount() > 0;
    }

    public void addTestElement(TestElement el) {
        if (el instanceof CookieManager) {
            setCookieManager((CookieManager) el);
        } else if (el instanceof CacheManager) {
            setCacheManager((CacheManager) el);
        } else if (el instanceof HeaderManager) {
            setHeaderManager((HeaderManager) el);
        } else if (el instanceof AuthManager) {
            setAuthManager((AuthManager) el);
        } else {
            super.addTestElement(el);
        }
    }

    /**
     * {@inheritDoc}
     * <p>
     * Clears the Header Manager property so subsequent loops don't keep merging
     * more elements
     */

    public void clearTestElementChildren() {
        removeProperty(HEADER_MANAGER);
    }

    public void setPort(int value) {
        setProperty(new IntegerProperty(PORT, value));
    }

    /**
     * Get the port number for a URL, applying defaults if necessary. (Called by
     * CookieManager.)
     * 
     * @param protocol
     *            from {@link URL#getProtocol()}
     * @param port
     *            number from {@link URL#getPort()}
     * @return the default port for the protocol
     */
    public static int getDefaultPort(String protocol, int port) {
        if (port == URL_UNSPECIFIED_PORT) {
            return protocol.equalsIgnoreCase(HTTPConstants.PROTOCOL_HTTP) ? HTTPConstants.DEFAULT_HTTP_PORT
                    : protocol.equalsIgnoreCase(HTTPConstants.PROTOCOL_HTTPS) ? HTTPConstants.DEFAULT_HTTPS_PORT
                            : port;
        }
        return port;
    }

    /**
     * Get the port number from the port string, allowing for trailing blanks.
     * 
     * @return port number or UNSPECIFIED_PORT (== 0)
     */
    public int getPortIfSpecified() {
        String port_s = getPropertyAsString(PORT, UNSPECIFIED_PORT_AS_STRING);
        try {
            return Integer.parseInt(port_s.trim());
        } catch (NumberFormatException e) {
            return UNSPECIFIED_PORT;
        }
    }

    /**
     * Tell whether the default port for the specified protocol is used
     * 
     * @return true if the default port number for the protocol is used, false
     *         otherwise
     */
    public boolean isProtocolDefaultPort() {
        final int port = getPortIfSpecified();
        final String protocol = getProtocol();
        if (port == UNSPECIFIED_PORT
                || (HTTPConstants.PROTOCOL_HTTP.equalsIgnoreCase(protocol)
                        && port == HTTPConstants.DEFAULT_HTTP_PORT)
                || (HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(protocol)
                        && port == HTTPConstants.DEFAULT_HTTPS_PORT)) {
            return true;
        }
        return false;
    }

    /**
     * Get the port; apply the default for the protocol if necessary.
     * 
     * @return the port number, with default applied if required.
     */
    public int getPort() {
        final int port = getPortIfSpecified();
        if (port == UNSPECIFIED_PORT) {
            String prot = getProtocol();
            if (HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(prot)) {
                return HTTPConstants.DEFAULT_HTTPS_PORT;
            }
            if (!HTTPConstants.PROTOCOL_HTTP.equalsIgnoreCase(prot)) {
                log.warn("Unexpected protocol: " + prot);
                // TODO - should this return something else?
            }
            return HTTPConstants.DEFAULT_HTTP_PORT;
        }
        return port;
    }

    public void setDomain(String value) {
        setProperty(DOMAIN, value);
    }

    public String getDomain() {
        return getPropertyAsString(DOMAIN);
    }

    public void setConnectTimeout(String value) {
        setProperty(CONNECT_TIMEOUT, value, "");
    }

    public int getConnectTimeout() {
        return getPropertyAsInt(CONNECT_TIMEOUT, 0);
    }

    public void setResponseTimeout(String value) {
        setProperty(RESPONSE_TIMEOUT, value, "");
    }

    public int getResponseTimeout() {
        return getPropertyAsInt(RESPONSE_TIMEOUT, 0);
    }

    public String getProxyHost() {
        return getPropertyAsString(PROXYHOST);
    }

    public int getProxyPortInt() {
        return getPropertyAsInt(PROXYPORT, 0);
    }

    public String getProxyUser() {
        return getPropertyAsString(PROXYUSER);
    }

    public String getProxyPass() {
        return getPropertyAsString(PROXYPASS);
    }

    public void setArguments(Arguments value) {
        setProperty(new TestElementProperty(ARGUMENTS, value));
    }

    public Arguments getArguments() {
        return (Arguments) getProperty(ARGUMENTS).getObjectValue();
    }

    /**
     * @param value
     *            Boolean that indicates body will be sent as is
     */
    public void setPostBodyRaw(boolean value) {
        setProperty(POST_BODY_RAW, value, POST_BODY_RAW_DEFAULT);
    }

    /**
     * @return boolean that indicates body will be sent as is
     */
    public boolean getPostBodyRaw() {
        return getPropertyAsBoolean(POST_BODY_RAW, POST_BODY_RAW_DEFAULT);
    }

    public void setAuthManager(AuthManager value) {
        AuthManager mgr = getAuthManager();
        if (mgr != null) {
            log.warn("Existing AuthManager " + mgr.getName() + " superseded by " + value.getName());
        }
        setProperty(new TestElementProperty(AUTH_MANAGER, value));
    }

    public AuthManager getAuthManager() {
        return (AuthManager) getProperty(AUTH_MANAGER).getObjectValue();
    }

    public void setHeaderManager(HeaderManager value) {
        HeaderManager mgr = getHeaderManager();
        if (mgr != null) {
            value = mgr.merge(value, true);
            if (log.isDebugEnabled()) {
                log.debug("Existing HeaderManager '" + mgr.getName() + "' merged with '" + value.getName() + "'");
                for (int i = 0; i < value.getHeaders().size(); i++) {
                    log.debug("    " + value.getHeader(i).getName() + "=" + value.getHeader(i).getValue());
                }
            }
        }
        setProperty(new TestElementProperty(HEADER_MANAGER, value));
    }

    public HeaderManager getHeaderManager() {
        return (HeaderManager) getProperty(HEADER_MANAGER).getObjectValue();
    }

    // private method to allow AsyncSample to reset the value without performing
    // checks
    private void setCookieManagerProperty(CookieManager value) {
        setProperty(new TestElementProperty(COOKIE_MANAGER, value));
    }

    public void setCookieManager(CookieManager value) {
        CookieManager mgr = getCookieManager();
        if (mgr != null) {
            log.warn("Existing CookieManager " + mgr.getName() + " superseded by " + value.getName());
        }
        setCookieManagerProperty(value);
    }

    public CookieManager getCookieManager() {
        return (CookieManager) getProperty(COOKIE_MANAGER).getObjectValue();
    }

    // private method to allow AsyncSample to reset the value without performing
    // checks
    private void setCacheManagerProperty(CacheManager value) {
        setProperty(new TestElementProperty(CACHE_MANAGER, value));
    }

    public void setCacheManager(CacheManager value) {
        CacheManager mgr = getCacheManager();
        if (mgr != null) {
            log.warn("Existing CacheManager " + mgr.getName() + " superseded by " + value.getName());
        }
        setCacheManagerProperty(value);
    }

    public CacheManager getCacheManager() {
        return (CacheManager) getProperty(CACHE_MANAGER).getObjectValue();
    }

    public boolean isImageParser() {
        return getPropertyAsBoolean(IMAGE_PARSER, false);
    }

    public void setImageParser(boolean parseImages) {
        setProperty(IMAGE_PARSER, parseImages, false);
    }

    /**
     * Get the regular expression URLs must match.
     * 
     * @return regular expression (or empty) string
     */
    public String getEmbeddedUrlRE() {
        return getPropertyAsString(EMBEDDED_URL_RE, "");
    }

    public void setEmbeddedUrlRE(String regex) {
        setProperty(new StringProperty(EMBEDDED_URL_RE, regex));
    }

    /**
     * Populates the provided HTTPSampleResult with details from the Exception.
     * Does not create a new instance, so should not be used directly to add a
     * subsample.
     * 
     * @param e
     *            Exception representing the error.
     * @param res
     *            SampleResult to be modified
     * @return the modified sampling result containing details of the Exception.
     */
    protected HTTPSampleResult errorResult(Throwable e, HTTPSampleResult res) {
        res.setSampleLabel("Error: " + res.getSampleLabel());
        res.setDataType(SampleResult.TEXT);
        ByteArrayOutputStream text = new ByteArrayOutputStream(200);
        e.printStackTrace(new PrintStream(text));
        res.setResponseData(text.toByteArray());
        res.setResponseCode(NON_HTTP_RESPONSE_CODE + ": " + e.getClass().getName());
        res.setResponseMessage(NON_HTTP_RESPONSE_MESSAGE + ": " + e.getMessage());
        res.setSuccessful(false);
        res.setMonitor(this.isMonitor());
        return res;
    }

    private static final String HTTP_PREFIX = HTTPConstants.PROTOCOL_HTTP + "://"; // $NON-NLS-1$
    private static final String HTTPS_PREFIX = HTTPConstants.PROTOCOL_HTTPS + "://"; // $NON-NLS-1$

    // Bug 51939
    private static final boolean SEPARATE_CONTAINER = JMeterUtils.getPropDefault("httpsampler.separate.container",
            true); // $NON-NLS-1$

    /**
     * Get the URL, built from its component parts.
     * 
     * <p>
     * As a special case, if the path starts with "http[s]://", then the path is
     * assumed to be the entire URL.
     * </p>
     * 
     * @return The URL to be requested by this sampler.
     * @throws MalformedURLException
     */
    public URL getUrl() throws MalformedURLException {
        StringBuilder pathAndQuery = new StringBuilder(100);
        String path = this.getPath();
        // Hack to allow entire URL to be provided in host field
        if (path.startsWith(HTTP_PREFIX) || path.startsWith(HTTPS_PREFIX)) {
            return new URL(path);
        }
        String domain = getDomain();
        String protocol = getProtocol();
        if (PROTOCOL_FILE.equalsIgnoreCase(protocol)) {
            domain = null; // allow use of relative file URLs
        } else {
            // HTTP URLs must be absolute, allow file to be relative
            if (!path.startsWith("/")) { // $NON-NLS-1$
                pathAndQuery.append("/"); // $NON-NLS-1$
            }
        }
        pathAndQuery.append(path);

        // Add the query string if it is a HTTP GET or DELETE request
        if (HTTPConstants.GET.equals(getMethod()) || HTTPConstants.DELETE.equals(getMethod())) {
            // Get the query string encoded in specified encoding
            // If no encoding is specified by user, we will get it
            // encoded in UTF-8, which is what the HTTP spec says
            String queryString = getQueryString(getContentEncoding());
            if (queryString.length() > 0) {
                if (path.indexOf(QRY_PFX) > -1) {// Already contains a prefix
                    pathAndQuery.append(QRY_SEP);
                } else {
                    pathAndQuery.append(QRY_PFX);
                }
                pathAndQuery.append(queryString);
            }
        }
        // If default port for protocol is used, we do not include port in URL
        if (isProtocolDefaultPort()) {
            return new URL(protocol, domain, pathAndQuery.toString());
        }
        return new URL(protocol, domain, getPort(), pathAndQuery.toString());
    }

    /**
     * Gets the QueryString attribute of the UrlConfig object, using UTF-8 to
     * encode the URL
     * 
     * @return the QueryString value
     */
    public String getQueryString() {
        // We use the encoding which should be used according to the HTTP spec,
        // which is UTF-8
        return getQueryString(EncoderCache.URL_ARGUMENT_ENCODING);
    }

    /**
     * Gets the QueryString attribute of the UrlConfig object, using the
     * specified encoding to encode the parameter values put into the URL
     * 
     * @param contentEncoding
     *            the encoding to use for encoding parameter values
     * @return the QueryString value
     */
    public String getQueryString(String contentEncoding) {
        // Check if the sampler has a specified content encoding
        if (JOrphanUtils.isBlank(contentEncoding)) {
            // We use the encoding which should be used according to the HTTP
            // spec, which is UTF-8
            contentEncoding = EncoderCache.URL_ARGUMENT_ENCODING;
        }
        StringBuilder buf = new StringBuilder();
        PropertyIterator iter = getArguments().iterator();
        boolean first = true;
        while (iter.hasNext()) {
            HTTPArgument item = null;
            /*
             * N.B. Revision 323346 introduced the ClassCast check, but then
             * used iter.next() to fetch the item to be cast, thus skipping the
             * element that did not cast. Reverted to work more like the
             * original code, but with the check in place. Added a warning
             * message so can track whether it is necessary
             */
            Object objectValue = iter.next().getObjectValue();
            try {
                item = (HTTPArgument) objectValue;
            } catch (ClassCastException e) {
                log.warn("Unexpected argument type: " + objectValue.getClass().getName());
                item = new HTTPArgument((Argument) objectValue);
            }
            final String encodedName = item.getEncodedName();
            if (encodedName.length() == 0) {
                continue; // Skip parameters with a blank name (allows use of
                          // optional variables in parameter lists)
            }
            if (!first) {
                buf.append(QRY_SEP);
            } else {
                first = false;
            }
            buf.append(encodedName);
            if (item.getMetaData() == null) {
                buf.append(ARG_VAL_SEP);
            } else {
                buf.append(item.getMetaData());
            }

            // Encode the parameter value in the specified content encoding
            try {
                buf.append(item.getEncodedValue(contentEncoding));
            } catch (UnsupportedEncodingException e) {
                log.warn("Unable to encode parameter in encoding " + contentEncoding
                        + ", parameter value not included in query string");
            }
        }
        return buf.toString();
    }

    // Mark Walsh 2002-08-03, modified to also parse a parameter name value
    // string, where string contains only the parameter name and no equal sign.
    /**
     * This method allows a proxy server to send over the raw text from a
     * browser's output stream to be parsed and stored correctly into the
     * UrlConfig object.
     * 
     * For each name found, addArgument() is called
     * 
     * @param queryString
     *            - the query string, might be the post body of a http post
     *            request.
     * @param contentEncoding
     *            - the content encoding of the query string; if non-null then
     *            it is used to decode the
     */
    public void parseArguments(String queryString, String contentEncoding) {
        String[] args = JOrphanUtils.split(queryString, QRY_SEP);
        for (int i = 0; i < args.length; i++) {
            // need to handle four cases:
            // - string contains name=value
            // - string contains name=
            // - string contains name
            // - empty string

            String metaData; // records the existance of an equal sign
            String name;
            String value;
            int length = args[i].length();
            int endOfNameIndex = args[i].indexOf(ARG_VAL_SEP);
            if (endOfNameIndex != -1) {// is there a separator?
                // case of name=value, name=
                metaData = ARG_VAL_SEP;
                name = args[i].substring(0, endOfNameIndex);
                value = args[i].substring(endOfNameIndex + 1, length);
            } else {
                metaData = "";
                name = args[i];
                value = "";
            }
            if (name.length() > 0) {
                // If we know the encoding, we can decode the argument value,
                // to make it easier to read for the user
                if (!StringUtils.isEmpty(contentEncoding)) {
                    addEncodedArgument(name, value, metaData, contentEncoding);
                } else {
                    // If we do not know the encoding, we just use the encoded
                    // value
                    // The browser has already done the encoding, so save the
                    // values as is
                    addNonEncodedArgument(name, value, metaData);
                }
            }
        }
    }

    public void parseArguments(String queryString) {
        // We do not know the content encoding of the query string
        parseArguments(queryString, null);
    }

    public String toString() {
        try {
            StringBuilder stringBuffer = new StringBuilder();
            stringBuffer.append(this.getUrl().toString());
            // Append body if it is a post or put
            if (HTTPConstants.POST.equals(getMethod()) || HTTPConstants.PUT.equals(getMethod())) {
                stringBuffer.append("\nQuery Data: ");
                stringBuffer.append(getQueryString());
            }
            return stringBuffer.toString();
        } catch (MalformedURLException e) {
            return "";
        }
    }

    /**
     * Do a sampling and return its results.
     * 
     * @param e
     *            <code>Entry</code> to be sampled
     * @return results of the sampling
     */

    public SampleResult sample(Entry e) {
        return sample();
    }

    /**
     * Perform a sample, and return the results
     * 
     * @return results of the sampling
     */
    public SampleResult sample() {
        SampleResult res = null;
        Configuration configuration = ClassifierController.getActualConfiguration();
        String confName = "";
        if (configuration == null) {
            confName = "Default";
        } else {
            confName = configuration.getConfigurationName();
        }
        try {
            res = sample(getUrl(), getMethod(), false, 0);
            res.setSampleLabel(confName + "@" + ResultDataSet.getBatery() + "@"
                    + JMeterContextService.getTotalThreads() + "@" + getName());
            return res;
        } catch (Exception e) {
            return errorResult(e, new HTTPSampleResult());
        }
    }

    /**
     * Samples the URL passed in and stores the result in
     * <code>HTTPSampleResult</code>, following redirects and downloading page
     * resources as appropriate.
     * <p>
     * When getting a redirect target, redirects are not followed and resources
     * are not downloaded. The caller will take care of this.
     * 
     * @param u
     *            URL to sample
     * @param method
     *            HTTP method: GET, POST,...
     * @param areFollowingRedirect
     *            whether we're getting a redirect target
     * @param depth
     *            Depth of this target in the frame structure. Used only to
     *            prevent infinite recursion.
     * @return results of the sampling
     */
    protected abstract HTTPSampleResult sample(URL u, String method, boolean areFollowingRedirect, int depth);

    /**
     * Download the resources of an HTML page.
     * 
     * @param res
     *            result of the initial request - must contain an HTML response
     * @param container
     *            for storing the results, if any
     * @param frameDepth
     *            Depth of this target in the frame structure. Used only to
     *            prevent infinite recursion.
     * @return res if no resources exist, otherwise the "Container" result with
     *         one subsample per request issued
     */
    protected HTTPSampleResult downloadPageResources(HTTPSampleResult res, HTTPSampleResult container,
            int frameDepth) {
        Iterator<URL> urls = null;
        try {
            final byte[] responseData = res.getResponseData();
            if (responseData.length > 0) { // Bug 39205
                String parserName = getParserClass(res);
                if (parserName != null) {
                    final HTMLParser parser = parserName.length() > 0 ? // we
                    // have
                    // a
                    // name
                            HTMLParser.getParser(parserName) : HTMLParser.getParser(); // we don't; use the
                    // default parser
                    urls = parser.getEmbeddedResourceURLs(responseData, res.getURL(),
                            res.getDataEncodingWithDefault());
                }
            }
        } catch (HTMLParseException e) {
            // Don't break the world just because this failed:
            res.addSubResult(errorResult(e, new HTTPSampleResult(res)));
            setParentSampleSuccess(res, false);
        }

        // Iterate through the URLs and download each image:
        if (urls != null && urls.hasNext()) {
            if (container == null) {
                // TODO needed here because currently done on sample completion
                // in JMeterThread,
                // but that only catches top-level samples.
                res.setThreadName(Thread.currentThread().getName());
                container = new HTTPSampleResult(res);
                container.addRawSubResult(res);
            }
            res = container;

            // Get the URL matcher
            String re = getEmbeddedUrlRE();
            Perl5Matcher localMatcher = null;
            Pattern pattern = null;
            if (re.length() > 0) {
                try {
                    pattern = JMeterUtils.getPattern(re);
                    localMatcher = JMeterUtils.getMatcher();// don't fetch
                    // unless pattern
                    // compiles
                } catch (MalformedCachePatternException e) {
                    log.warn("Ignoring embedded URL match string: " + e.getMessage());
                }
            }

            // For concurrent get resources
            final List<Callable<AsynSamplerResultHolder>> liste = new ArrayList<Callable<AsynSamplerResultHolder>>();

            while (urls.hasNext()) {
                Object binURL = urls.next(); // See catch clause below
                try {
                    URL url = (URL) binURL;
                    if (url == null) {
                        log.warn("Null URL detected (should not happen)");
                    } else {
                        String urlstr = url.toString();
                        String urlStrEnc = encodeSpaces(urlstr);
                        if (!urlstr.equals(urlStrEnc)) {// There were some
                            // spaces in the URL
                            try {
                                url = new URL(urlStrEnc);
                            } catch (MalformedURLException e) {
                                res.addSubResult(errorResult(new Exception(urlStrEnc + " is not a correct URI"),
                                        new HTTPSampleResult(res)));
                                setParentSampleSuccess(res, false);
                                continue;
                            }
                        }
                        // I don't think localMatcher can be null here, but
                        // check just in case
                        if (pattern != null && localMatcher != null && !localMatcher.matches(urlStrEnc, pattern)) {
                            continue; // we have a pattern and the URL does not
                                      // match, so skip it
                        }

                        if (isConcurrentDwn()) {
                            // if concurrent download emb. resources, add to a
                            // list for async gets later
                            liste.add(new ASyncSample(url, HTTPConstants.GET, false, frameDepth + 1,
                                    getCookieManager(), this));
                        } else {
                            // default: serial download embedded resources
                            HTTPSampleResult binRes = sample(url, HTTPConstants.GET, false, frameDepth + 1);
                            res.addSubResult(binRes);
                            setParentSampleSuccess(res, res.isSuccessful() && binRes.isSuccessful());
                        }

                    }
                } catch (ClassCastException e) { // TODO can this happen?
                    res.addSubResult(errorResult(new Exception(binURL + " is not a correct URI"),
                            new HTTPSampleResult(res)));
                    setParentSampleSuccess(res, false);
                    continue;
                }
            }
            // IF for download concurrent embedded resources
            if (isConcurrentDwn()) {
                int poolSize = CONCURRENT_POOL_SIZE; // init with default value
                try {
                    poolSize = Integer.parseInt(getConcurrentPool());
                } catch (NumberFormatException nfe) {
                    log.warn("Concurrent download resources selected, "// $NON-NLS-1$
                            + "but pool size value is bad. Use default value");// $NON-NLS-1$
                }
                // Thread pool Executor to get resources
                // use a LinkedBlockingQueue, note: max pool size doesn't effect
                final ThreadPoolExecutor exec = new ThreadPoolExecutor(poolSize, poolSize, KEEPALIVETIME,
                        TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadFactory() {

                            public Thread newThread(final Runnable r) {
                                Thread t = new CleanerThread(new Runnable() {

                                    public void run() {
                                        try {
                                            r.run();
                                        } finally {
                                            ((CleanerThread) Thread.currentThread()).notifyThreadEnd();
                                        }
                                    }
                                });
                                return t;
                            }
                        });

                boolean tasksCompleted = false;
                try {
                    // sample all resources with threadpool
                    final List<Future<AsynSamplerResultHolder>> retExec = exec.invokeAll(liste);
                    // call normal shutdown (wait ending all tasks)
                    exec.shutdown();
                    // put a timeout if tasks couldn't terminate
                    exec.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.SECONDS);
                    CookieManager cookieManager = getCookieManager();
                    // add result to main sampleResult
                    for (Future<AsynSamplerResultHolder> future : retExec) {
                        AsynSamplerResultHolder binRes;
                        try {
                            binRes = future.get(1, TimeUnit.MILLISECONDS);
                            if (cookieManager != null) {
                                CollectionProperty cookies = binRes.getCookies();
                                PropertyIterator iter = cookies.iterator();
                                while (iter.hasNext()) {
                                    Cookie cookie = (Cookie) iter.next().getObjectValue();
                                    cookieManager.add(cookie);
                                }
                            }
                            res.addSubResult(binRes.getResult());
                            setParentSampleSuccess(res, res.isSuccessful() && binRes.getResult().isSuccessful());
                        } catch (TimeoutException e) {
                            errorResult(e, res);
                        }
                    }
                    tasksCompleted = exec.awaitTermination(1, TimeUnit.MILLISECONDS); // did all the tasks finish?
                } catch (InterruptedException ie) {
                    log.warn("Interruped fetching embedded resources", ie); // $NON-NLS-1$
                } catch (ExecutionException ee) {
                    log.warn("Execution issue when fetching embedded resources", ee); // $NON-NLS-1$
                } finally {
                    if (!tasksCompleted) {
                        exec.shutdownNow(); // kill any remaining tasks
                    }
                }
            }
        }
        return res;
    }

    /**
     * Set parent successful attribute based on IGNORE_FAILED_EMBEDDED_RESOURCES
     * parameter
     * 
     * @param res
     *            {@link HTTPSampleResult}
     * @param initialValue
     *            boolean
     */
    private void setParentSampleSuccess(HTTPSampleResult res, boolean initialValue) {
        if (!IGNORE_FAILED_EMBEDDED_RESOURCES) {
            res.setSuccessful(initialValue);
        }
    }

    /*
     * @param res HTTPSampleResult to check
     * 
     * @return parser class name (may be "") or null if entry does not exist
     */
    private String getParserClass(HTTPSampleResult res) {
        final String ct = res.getMediaType();
        return parsersForType.get(ct);
    }

    // TODO: make static?
    protected String encodeSpaces(String path) {
        return JOrphanUtils.replaceAllChars(path, ' ', "%20"); // $NON-NLS-1$
    }

    /**
     * {@inheritDoc}
     */

    public void testEnded() {
        //System.out.println("finalizou");
    }

    /**
     * {@inheritDoc}
     */

    public void testEnded(String host) {
        testEnded();
    }

    /**
     * {@inheritDoc}
     */

    public void testIterationStart(LoopIterationEvent event) {
        if (!USE_CACHED_SSL_CONTEXT) {
            JsseSSLManager sslMgr = (JsseSSLManager) SSLManager.getInstance();
            sslMgr.resetContext();
            notifySSLContextWasReset();
        }
    }

    /**
     * Called by testIterationStart if the SSL Context was reset.
     * 
     * This implementation does nothing.
     */
    protected void notifySSLContextWasReset() {
        // NOOP
    }

    /**
     * {@inheritDoc}
     */

    public void testStarted() {

        //System.out.println("iniciou");

    }

    /**
     * {@inheritDoc}
     */

    public void testStarted(String host) {
        testStarted();
    }

    /**
     * {@inheritDoc}
     */

    public Object clone() {
        HTTPSamplerBaseClassifier base = (HTTPSamplerBaseClassifier) super.clone();
        return base;
    }

    /**
     * Iteratively download the redirect targets of a redirect response.
     * <p>
     * The returned result will contain one subsample for each request issued,
     * including the original one that was passed in. It will be an
     * HTTPSampleResult that should mostly look as if the final destination of
     * the redirect chain had been obtained in a single shot.
     * 
     * @param res
     *            result of the initial request - must be a redirect response
     * @param frameDepth
     *            Depth of this target in the frame structure. Used only to
     *            prevent infinite recursion.
     * @return "Container" result with one subsample per request issued
     */
    protected HTTPSampleResult followRedirects(HTTPSampleResult res, int frameDepth) {
        HTTPSampleResult totalRes = new HTTPSampleResult(res);
        totalRes.addRawSubResult(res);
        HTTPSampleResult lastRes = res;

        int redirect;
        for (redirect = 0; redirect < MAX_REDIRECTS; redirect++) {
            boolean invalidRedirectUrl = false;
            String location = lastRes.getRedirectLocation();
            if (REMOVESLASHDOTDOT) {
                location = ConversionUtils.removeSlashDotDot(location);
            }
            // Browsers seem to tolerate Location headers with spaces,
            // replacing them automatically with %20. We want to emulate
            // this behaviour.
            location = encodeSpaces(location);
            try {
                lastRes = sample(ConversionUtils.makeRelativeURL(lastRes.getURL(), location), HTTPConstants.GET,
                        true, frameDepth);
            } catch (MalformedURLException e) {
                errorResult(e, lastRes);
                // The redirect URL we got was not a valid URL
                invalidRedirectUrl = true;
            }
            if (lastRes.getSubResults() != null && lastRes.getSubResults().length > 0) {
                SampleResult[] subs = lastRes.getSubResults();
                for (int i = 0; i < subs.length; i++) {
                    totalRes.addSubResult(subs[i]);
                }
            } else {
                // Only add sample if it is a sample of valid url redirect, i.e.
                // that
                // we have actually sampled the URL
                if (!invalidRedirectUrl) {
                    totalRes.addSubResult(lastRes);
                }
            }

            if (!lastRes.isRedirect()) {
                break;
            }
        }
        if (redirect >= MAX_REDIRECTS) {
            lastRes = errorResult(new IOException("Exceeeded maximum number of redirects: " + MAX_REDIRECTS),
                    new HTTPSampleResult(lastRes));
            totalRes.addSubResult(lastRes);
        }

        // Now populate the any totalRes fields that need to
        // come from lastRes:
        totalRes.setSampleLabel(totalRes.getSampleLabel() + "->" + lastRes.getSampleLabel());
        // The following three can be discussed: should they be from the
        // first request or from the final one? I chose to do it this way
        // because that's what browsers do: they show the final URL of the
        // redirect chain in the location field.
        totalRes.setURL(lastRes.getURL());
        totalRes.setHTTPMethod(lastRes.getHTTPMethod());
        totalRes.setQueryString(lastRes.getQueryString());
        totalRes.setRequestHeaders(lastRes.getRequestHeaders());

        totalRes.setResponseData(lastRes.getResponseData());
        totalRes.setResponseCode(lastRes.getResponseCode());
        totalRes.setSuccessful(lastRes.isSuccessful());
        totalRes.setResponseMessage(lastRes.getResponseMessage());
        totalRes.setDataType(lastRes.getDataType());
        totalRes.setResponseHeaders(lastRes.getResponseHeaders());
        totalRes.setContentType(lastRes.getContentType());
        totalRes.setDataEncoding(lastRes.getDataEncodingNoDefault());
        return totalRes;
    }

    /**
     * Follow redirects and download page resources if appropriate. this works,
     * but the container stuff here is what's doing it. followRedirects() is
     * actually doing the work to make sure we have only one container to make
     * this work more naturally, I think this method - sample() - needs to take
     * an HTTPSamplerResult container parameter instead of a
     * boolean:areFollowingRedirect.
     * 
     * @param areFollowingRedirect
     * @param frameDepth
     * @param res
     * @return the sample result
     */
    protected HTTPSampleResult resultProcessing(boolean areFollowingRedirect, int frameDepth,
            HTTPSampleResult res) {

        boolean wasRedirected = false;
        if (!areFollowingRedirect) {
            if (res.isRedirect()) {
                log.debug("Location set to - " + res.getRedirectLocation());

                if (getFollowRedirects()) {
                    res = followRedirects(res, frameDepth);
                    areFollowingRedirect = true;
                    wasRedirected = true;
                }
            }
        }
        if (isImageParser() && (SampleResult.TEXT).equals(res.getDataType()) && res.isSuccessful()) {
            if (frameDepth > MAX_FRAME_DEPTH) {
                res.addSubResult(errorResult(new Exception("Maximum frame/iframe nesting depth exceeded."),
                        new HTTPSampleResult(res)));
            } else {
                // Only download page resources if we were not redirected.
                // If we were redirected, the page resources have already been
                // downloaded for the sample made for the redirected url
                // otherwise, use null so the container is created if necessary
                // unless
                // the flag is false, in which case revert to broken 2.1
                // behaviour
                // Bug 51939 -
                // https://issues.apache.org/bugzilla/show_bug.cgi?id=51939
                if (!wasRedirected) {
                    HTTPSampleResult container = (HTTPSampleResult) (areFollowingRedirect ? res.getParent()
                            : SEPARATE_CONTAINER ? null : res);
                    res = downloadPageResources(res, container, frameDepth);
                }
            }
        }

        /*ResultDataSet result = new ResultDataSet();
            
        String body;
        try {
           body = new String(res.getResponseData(), "UTF-8");
           result.setResponseBody(body);
           result.setType(this.getType());
           result.setHeader(res.getResponseHeaders());
            
           result.setProgram(this.getProgram());
           result.setFunction(this.getFunction());
           result.setGoal(this.getGoal());
           result.setServer(this.getServer());
           result.setResponseCode(res.getResponseCode());
            
        } catch (UnsupportedEncodingException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
        }
            
        result.setTimeStamp(res.getTimeStamp());
            
        result.setBytes(res.getBytes());
            
        result.setBodySize(res.getBodySize());
            
        result.setThreads(res.getAllThreads());
            
        result.setLatency(res.getLatency());
            
        result.setSamplerCount(res.getSampleCount());
            
        result.setResult(res);
            
        result.setGroupThreads(res.getGroupThreads());
            
        result.setErrors(res.getErrorCount());
            
        result.setHeaderSize(res.getHeadersSize());
            
        result.setResponseTime(res.getTime());
            
        result.setUrl(ResultDataSet.getWords(res.getUrlAsString()));
            
        ResultDataSet.getResults().add(result);
            
        /*
         * if (JMeterContextService.getContext().getEngine() instanceof
         * ClientJMeterEngine) {
         * 
         * JMeterTreeNode node = (JMeterTreeNode) GuiPackage.getInstance()
         * .getTreeModel().getRoot(); JMeterTreeNode node1 = (JMeterTreeNode)
         * node.getChildAt(0); JMeterTreeNode node2 = (JMeterTreeNode)
         * node1.getChildAt(0); ThreadGroup group = (ThreadGroup)
         * node2.getTestElement();
         * 
         * result.setThreadNumber(group
         * .getPropertyAsInt("ThreadGroup.num_threads")); }
         */

        /*result.setThreadNumber(JMeterContextService.getTotalThreads());
        // ResultDataSet.addResultDecisionTestFile(result);
            
        if (res.getBodySize() > ResultDataSet.maxbodysize) {
           ResultDataSet.maxbodysize = res.getBodySize();
        }
            
        if (res.getBytes() > ResultDataSet.getMaxbytes()) {
           ResultDataSet.maxbytes = res.getBytes();
        }
            
        if (res.getHeadersSize() > ResultDataSet.maxheadersize) {
           ResultDataSet.maxheadersize = res.getHeadersSize();
        }
            
        if (res.getLatency() > ResultDataSet.getMaxlatencia()) {
           ResultDataSet.maxlatencia = res.getLatency();
        }
            
        if (res.getTime() > ResultDataSet.maxmax) {
           ResultDataSet.maxmax = res.getTime();
        }
            
        if (res.getTime() < ResultDataSet.maxmin) {
           ResultDataSet.maxmin = res.getTime();
        }
            
        result.setThreadNumber(JMeterContextService.getTotalThreads());*/

        //if (res.getTime() > ResultDataSet.getTimeLearning()) {
        //ResultDataSet.setTimeLearning(res.getTime());
        //}
        //Configuration configuration = ClassifierController
        //   .getActualConfiguration();

        //String confName = "";
        //if (configuration == null) {
        //confName = "Default";
        //} else {
        //confName = configuration.getConfigurationName();
        //}

        // result.setSampleLabel(confName + "@"
        // + JMeterContextService.getTotalThreads() + "@"
        // + res.getSampleLabel());

        //System.out.print("Add resul;t file");
        //ResultDataSet.addResultDecisionTestFile(result);

        // if (result.getFunction().equals("Aprendizado")) {
        // if (res.getTime() > ResultDataSet.getTimeLearning()) {
        // ResultDataSet.setTimeLearning(res.getTime());
        // }
        // ResultDataSet.addResultDecisionTestFile(result);
        // }

        // res.setSampleLabel(confName + "@"
        // + JMeterContextService.getTotalThreads() + "@"
        // + res.getSampleLabel());

        return res;
    }

    /**
     * Determine if the HTTP status code is successful or not i.e. in range 200
     * to 399 inclusive
     * 
     * @return whether in range 200-399 or not
     */
    protected boolean isSuccessCode(int code) {
        return (code >= 200 && code <= 399);
    }

    protected static String encodeBackSlashes(String value) {
        StringBuilder newValue = new StringBuilder();
        for (int i = 0; i < value.length(); i++) {
            char charAt = value.charAt(i);
            if (charAt == '\\') { // $NON-NLS-1$
                newValue.append("\\\\"); // $NON-NLS-1$
            } else {
                newValue.append(charAt);
            }
        }
        return newValue.toString();
    }

    /*
     * Method to set files list to be uploaded.
     * 
     * @param value HTTPFileArgs object that stores file list to be uploaded.
     */
    private void setHTTPFileArgs(HTTPFileArgs value) {
        if (value.getHTTPFileArgCount() > 0) {
            setProperty(new TestElementProperty(FILE_ARGS, value));
        } else {
            removeProperty(FILE_ARGS); // no point saving an empty list
        }
    }

    /*
     * Method to get files list to be uploaded.
     */
    private HTTPFileArgs getHTTPFileArgs() {
        return (HTTPFileArgs) getProperty(FILE_ARGS).getObjectValue();
    }

    /**
     * Get the collection of files as a list. The list is built up from the
     * filename/filefield/mimetype properties, plus any additional entries saved
     * in the FILE_ARGS property.
     * 
     * If there are no valid file entries, then an empty list is returned.
     * 
     * @return an array of file arguments (never null)
     */
    public HTTPFileArg[] getHTTPFiles() {
        final HTTPFileArgs fileArgs = getHTTPFileArgs();
        return fileArgs == null ? new HTTPFileArg[] {} : fileArgs.asArray();
    }

    public int getHTTPFileCount() {
        return getHTTPFiles().length;
    }

    /**
     * Saves the list of files. The first file is saved in the
     * Filename/field/mimetype properties. Any additional files are saved in the
     * FILE_ARGS array.
     * 
     * @param files
     *            list of files to save
     */
    public void setHTTPFiles(HTTPFileArg[] files) {
        HTTPFileArgs fileArgs = new HTTPFileArgs();
        // Weed out the empty files
        if (files.length > 0) {
            for (int i = 0; i < files.length; i++) {
                HTTPFileArg file = files[i];
                if (file.isNotEmpty()) {
                    fileArgs.addHTTPFileArg(file);
                }
            }
        }
        setHTTPFileArgs(fileArgs);
    }

    public static String[] getValidMethodsAsArray() {
        return METHODLIST.toArray(new String[METHODLIST.size()]);
    }

    public static boolean isSecure(String protocol) {
        return HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(protocol);
    }

    public static boolean isSecure(URL url) {
        return isSecure(url.getProtocol());
    }

    // Implement these here, to avoid re-implementing for sub-classes
    // (previously these were implemented in all TestElements)

    public void threadStarted() {
    }

    public void threadFinished() {
    }

    /**
     * Read response from the input stream, converting to MD5 digest if the
     * useMD5 property is set.
     * 
     * For the MD5 case, the result byte count is set to the size of the
     * original response.
     * 
     * Closes the inputStream
     * 
     * @param sampleResult
     * @param in
     *            input stream
     * @param length
     *            expected input length or zero
     * @return the response or the MD5 of the response
     * @throws IOException
     */
    public byte[] readResponse(SampleResult sampleResult, InputStream in, int length) throws IOException {
        try {
            byte[] readBuffer = new byte[8192]; // 8kB is the (max) size to have
            // the latency ('the first
            // packet')
            int bufferSize = 32;// Enough for MD5

            MessageDigest md = null;
            boolean asMD5 = useMD5();
            if (asMD5) {
                try {
                    md = MessageDigest.getInstance("MD5"); //$NON-NLS-1$
                } catch (NoSuchAlgorithmException e) {
                    log.error("Should not happen - could not find MD5 digest", e);
                    asMD5 = false;
                }
            } else {
                if (length <= 0) {// may also happen if long value > int.max
                    bufferSize = 4 * 1024;
                } else {
                    bufferSize = length;
                }
            }
            ByteArrayOutputStream w = new ByteArrayOutputStream(bufferSize);
            int bytesRead = 0;
            int totalBytes = 0;
            boolean first = true;
            while ((bytesRead = in.read(readBuffer)) > -1) {
                if (first) {
                    sampleResult.latencyEnd();
                    first = false;
                }
                if (asMD5 && md != null) {
                    md.update(readBuffer, 0, bytesRead);
                    totalBytes += bytesRead;
                } else {
                    w.write(readBuffer, 0, bytesRead);
                }
            }
            if (first) { // Bug 46838 - if there was no data, still need to set
                         // latency
                sampleResult.latencyEnd();
            }
            in.close();
            w.flush();
            if (asMD5 && md != null) {
                byte[] md5Result = md.digest();
                w.write(JOrphanUtils.baToHexBytes(md5Result));
                sampleResult.setBytes(totalBytes);
            }
            w.close();
            return w.toByteArray();
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    /**
     * JMeter 2.3.1 and earlier only had fields for one file on the GUI: -
     * FILE_NAME - FILE_FIELD - MIMETYPE These were stored in their own
     * individual properties.
     * 
     * Version 2.3.3 introduced a list of files, each with their own path, name
     * and mimetype.
     * 
     * In order to maintain backwards compatibility of test plans, the 3
     * original properties were retained; additional file entries are stored in
     * an HTTPFileArgs class. The HTTPFileArgs class was only present if there
     * is more than 1 file; this means that such test plans are backward
     * compatible.
     * 
     * Versions after 2.3.4 dispense with the original set of 3 properties. Test
     * plans that use them are converted to use a single HTTPFileArgs list.
     * 
     * @see HTTPSamplerBaseConverter
     */
    void mergeFileProperties() {
        JMeterProperty fileName = getProperty(FILE_NAME);
        JMeterProperty paramName = getProperty(FILE_FIELD);
        JMeterProperty mimeType = getProperty(MIMETYPE);
        HTTPFileArg oldStyleFile = new HTTPFileArg(fileName, paramName, mimeType);

        HTTPFileArgs fileArgs = getHTTPFileArgs();

        HTTPFileArgs allFileArgs = new HTTPFileArgs();
        if (oldStyleFile.isNotEmpty()) { // OK, we have an old-style file
            // definition
            allFileArgs.addHTTPFileArg(oldStyleFile); // save it
            // Now deal with any additional file arguments
            if (fileArgs != null) {
                HTTPFileArg[] infiles = fileArgs.asArray();
                for (int i = 0; i < infiles.length; i++) {
                    allFileArgs.addHTTPFileArg(infiles[i]);
                }
            }
        } else {
            if (fileArgs != null) { // for new test plans that don't have
                // FILE/PARAM/MIME properties
                allFileArgs = fileArgs;
            }
        }
        // Updated the property lists
        setHTTPFileArgs(allFileArgs);
        removeProperty(FILE_FIELD);
        removeProperty(FILE_NAME);
        removeProperty(MIMETYPE);
    }

    /**
     * set IP source to use - does not apply to Java HTTP implementation
     * currently
     */
    public void setIpSource(String value) {
        setProperty(IP_SOURCE, value, "");
    }

    /**
     * get IP source to use - does not apply to Java HTTP implementation
     * currently
     */
    public String getIpSource() {
        return getPropertyAsString(IP_SOURCE, "");
    }

    /**
     * Return if used a concurrent thread pool to get embedded resources.
     * 
     * @return true if used
     */
    public boolean isConcurrentDwn() {
        return getPropertyAsBoolean(CONCURRENT_DWN, false);
    }

    public void setConcurrentDwn(boolean concurrentDwn) {
        setProperty(CONCURRENT_DWN, concurrentDwn, false);
    }

    /**
     * Get the pool size for concurrent thread pool to get embedded resources.
     * 
     * @return the pool size
     */
    public String getConcurrentPool() {
        return getPropertyAsString(CONCURRENT_POOL, CONCURRENT_POOL_DEFAULT);
    }

    public void setConcurrentPool(String poolSize) {
        setProperty(CONCURRENT_POOL, poolSize, CONCURRENT_POOL_DEFAULT);
    }

    /**
     * Callable class to sample asynchronously resources embedded
     * 
     */
    private static class ASyncSample implements Callable<AsynSamplerResultHolder> {
        final private URL url;
        final private String method;
        final private boolean areFollowingRedirect;
        final private int depth;
        private final HTTPSamplerBaseClassifier sampler;
        private final JMeterContext jmeterContextOfParentThread;

        ASyncSample(URL url, String method, boolean areFollowingRedirect, int depth, CookieManager cookieManager,
                HTTPSamplerBaseClassifier base) {
            this.url = url;
            this.method = method;
            this.areFollowingRedirect = areFollowingRedirect;
            this.depth = depth;
            this.sampler = (HTTPSamplerBaseClassifier) base.clone();
            // We don't want to use CacheManager clone but the parent one, and
            // CacheManager is Thread Safe
            CacheManager cacheManager = base.getCacheManager();
            if (cacheManager != null) {
                this.sampler.setCacheManagerProperty(cacheManager);
            }

            if (cookieManager != null) {
                CookieManager clonedCookieManager = (CookieManager) cookieManager.clone();
                this.sampler.setCookieManagerProperty(clonedCookieManager);
            }
            this.jmeterContextOfParentThread = JMeterContextService.getContext();
        }

        public AsynSamplerResultHolder call() {
            JMeterContextService.replaceContext(jmeterContextOfParentThread);
            ((CleanerThread) Thread.currentThread()).registerSamplerForEndNotification(sampler);
            HTTPSampleResult httpSampleResult = sampler.sample(url, method, areFollowingRedirect, depth);
            if (sampler.getCookieManager() != null) {
                CollectionProperty cookies = sampler.getCookieManager().getCookies();
                return new AsynSamplerResultHolder(httpSampleResult, cookies);
            } else {
                return new AsynSamplerResultHolder(httpSampleResult, new CollectionProperty());
            }
        }
    }

    /**
     * Custom thread implementation that
     * 
     */
    private static class CleanerThread extends Thread {
        private final List<HTTPSamplerBaseClassifier> samplersToNotify = new ArrayList<HTTPSamplerBaseClassifier>();

        /**
         * @param runnable
         *            Runnable
         */
        public CleanerThread(Runnable runnable) {
            super(runnable);
        }

        /**
         * Notify of thread end
         */
        public void notifyThreadEnd() {
            for (HTTPSamplerBaseClassifier samplerBase : samplersToNotify) {
                samplerBase.threadFinished();
            }
            samplersToNotify.clear();
        }

        /**
         * Register sampler to be notify at end of thread
         * 
         * @param sampler
         *            {@link HTTPSamplerBaseClassifier}
         */
        public void registerSamplerForEndNotification(HTTPSamplerBaseClassifier sampler) {
            this.samplersToNotify.add(sampler);
        }
    }

    /**
     * Holder of AsynSampler result
     */
    private static class AsynSamplerResultHolder {
        private final HTTPSampleResult result;
        private final CollectionProperty cookies;

        /**
         * @param result
         * @param cookies
         */
        public AsynSamplerResultHolder(HTTPSampleResult result, CollectionProperty cookies) {
            super();
            this.result = result;
            this.cookies = cookies;
        }

        /**
         * @return the result
         */
        public HTTPSampleResult getResult() {
            return result;
        }

        /**
         * @return the cookies
         */
        public CollectionProperty getCookies() {
            return cookies;
        }
    }

    /**
     * @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement)
     */

    public boolean applies(ConfigTestElement configElement) {
        String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue();
        return APPLIABLE_CONFIG_CLASSES.contains(guiClass);
    }
}