/*
 * Project: Mako
 * Copyright (c) Xerox Corporation 1999-2001. All rights reserved.  Xerox, The
 * Document Company, and the stylized X are trademarks of the Xerox
 * Corporation. All other products are trademarks of their respective
 * companies.
 *
 * All rights reserved
 * $Header: /home/cvspublic/jakarta-jmeter/src/org/apache/jmeter/protocol/http/sampler/HTTPSampler.java,v 1.14 2001/06/29 21:57:15 mstover1 Exp $
 */
package org.apache.jmeter.protocol.http.sampler;

import java.io.*;
import java.net.*;
import java.security.Security;
import java.util.*;

import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.protocol.http.control.*;
import org.apache.jmeter.protocol.http.config.UrlConfig;
import org.apache.jmeter.config.*;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Entry;

/**
 *  A sampler which understands all the parts necessary to read statistics about
 *  HTTP requests, including cookies and authentication.
 *
 *@author     Michael Stover
 *@created    $Date: 2001/06/29 21:57:15 $
 *@version    $Revision: 1.14 $
 */
public class HTTPSampler implements Sampler
{

	/**
	 *  Description of the Field
	 */
	public final static String ARGUMENTS = "httpsampler.Arguments";
	/**
	 *  Description of the Field
	 */
	public final static String URL = "httpsampler.URL";
	/**
	 *  Description of the Field
	 */
	public final static String POST = "httpsampler.POST";
	/**
	 *  Description of the Field
	 */
	public final static String GET = "httpsampler.GET";
	/**
	 *  Description of the Field
	 */
	public final static String FILE_NAME = "httpsampler.FILE_NAME";
	/**
	 *  Description of the Field
	 */
	public final static String FILE_FIELD = "httpsampler.FILE_FIELD";
	/**
	 *  Description of the Field
	 */
	public final static String FILE_DATA = "httpsampler.FILE_DATA";
	/**
	 *  Description of the Field
	 */
	public final static String FILE_MIMETYPE = "httpsampler.FILE_MIMETYPE";
	/**
	 *  Description of the Field
	 */
	public final static String CONTENT_TYPE = "httpsampler.CONTENT_TYPE";
	/**
	 *  Description of the Field
	 */
	public final static String NORMAL_FORM = "normal_form";
	/**
	 *  Description of the Field
	 */
	public final static String MULTIPART_FORM = "multipart_form";

	protected static String encoding = "iso-8859-1";

	/**
	 *  Constructor for the HTTPSampler object
	 */
	public HTTPSampler()
	{
	}

	/**
	 *  A convenience method to call <code>sample</code> with no redirection
	 *
	 * @param  e  <code>Entry</code> to be sampled
	 * @return    results of the sampling
         * @see       org.apache.jmeter.protocol.http.sampler.HTTPSampler.sample(org.apache.jmeter.samplers.Entry, boolean)
	 */
	public SampleResult sample(Entry e)
	{
		return sample(e, false);
	}

	/**
	 *  Send POST data from <code>Entry</code> to the open connection.
	 *
	 * @param  connection       <code>URLConnection</code> of where POST data should be
	 *		            sent
	 * @param  url              contains the query string for POST
	 * @exception  IOException  if an I/O exception occurs
	 */
	public void sendPostData(URLConnection connection, UrlConfig url)
			 throws IOException
	{
		((HttpURLConnection) connection).setRequestMethod("POST");
		String postData = url.getQueryString();
		connection.setRequestProperty("Content-length", "" + postData.length());
		connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
		connection.setDoOutput(true);

		PrintWriter out = new PrintWriter(connection.getOutputStream());
		out.print(postData);
		out.close();
	}

	/**
	 * Returns a <code>HttpURLConnection</code> with request method(GET or POST),
         * headers, cookies, authorization properly set for the URL request
	 *
	 * @param  u                <code>URL</code> of the URL request
	 * @param  url              <code>UrlConfig</code> of the URL request
	 * @param  e                <code>Entry</code> from which all other info can
         *			    be derived from e.g. cookies, header, authorization
	 * @return                  <code>HttpURLConnection</code> of the URL request
	 * @exception  IOException  if an I/O Exception occurs
	 */
	protected HttpURLConnection setupConnection(URL u, UrlConfig url, Entry e) throws IOException
	{
		HttpURLConnection conn;
		conn = (HttpURLConnection) u.openConnection();
		conn.setFollowRedirects(false);
		conn.setRequestMethod((String) url.getProperty(UrlConfig.METHOD));
		setConnectionHeaders(conn, u, (HeaderManager) e.getConfigElement(HeaderManager.class));
		setConnectionCookie(conn, u, (CookieManager) e.getConfigElement(CookieManager.class));
		setConnectionAuthorization(conn, u, (AuthManager) e.getConfigElement(AuthManager.class));
		return conn;
	}

	/**
	 *  Gets the UrlConfig attribute of the HTTPSampler object
	 *
	 * @param  e  Description of Parameter
	 * @return    The UrlConfig value
	 */
	protected UrlConfig getUrlConfig(Entry e)
	{
		return (UrlConfig) e.getConfigElement(UrlConfig.class);
	}

	/**
	 * This method will setup <code>HttpURLConnection</code> to handle post using 
         * <code>sendPostData</code> method if the URL request is actually a html form
         * that needs to be posted
	 *
	 * @param  redirected       does code<HttpURLConnection</code> allow redirection
	 * @param  url              contains query string for POST
	 * @param  conn             <HttpURLConnection> of the URL request
	 * @exception  IOException  if 
	 */
	protected void writeToStream(boolean redirected, UrlConfig url, HttpURLConnection conn) throws IOException
	{
		if (!redirected && url.getProperty(UrlConfig.METHOD).equals(UrlConfig.POST))
		{
			sendPostData(conn, url);
		}
	}

	/*
	 * Uploading a file - put in separate sampler
	 * else if (contentType.equals(MULTIPART_FORM))
	 * {
	 *
	 * }
	 * }
	 */

        /**
         * Extracts all the required cookies for that particular URL request and set them
         * in the <code>HttpURLConnection</code> passed in
         *
         * @param conn		<code>HttpUrlConnection</code> which represents the 
	 *			URL request
         * @param u		<code>URL</code> of the URL request
         * @param cookieManager	the <code>CookieManager</code> containing all the 
         *			cookies for this <code>UrlConfig</code>
         */
	private void setConnectionCookie(HttpURLConnection conn, URL u, CookieManager cookieManager)
	{
		if (cookieManager != null)
		{
			String cookieHeader = cookieManager.getCookieHeaderForURL(u);
			if (cookieHeader != null)
			{
				conn.setRequestProperty("Cookie", cookieHeader);
			}
		}
	}

        /**
         * Extracts all the required headers for that particular URL request and set them
         * in the <code>HttpURLConnection</code> passed in
         *
         * @param conn		<code>HttpUrlConnection</code> which represents the 
	 *			URL request
         * @param u		<code>URL</code> of the URL request
         * @param headerManager	the <code>HeaderManager</code> containing all the 
         *			cookies for this <code>UrlConfig</code>
         */
	private void setConnectionHeaders(HttpURLConnection conn, URL u, HeaderManager headerManager)
	{
		if (headerManager != null)
		{
			Collection headers = headerManager.getHeaders();
			if (headers != null)
			{
				Iterator i = headers.iterator();
				while (i.hasNext())
				{
					Header header = (Header) i.next();
					conn.setRequestProperty(header.getName(), header.getValue());
				}
			}
		}
	}

        /**
         * Extracts all the required authorization for that particular URL request and set 
	 * them in the <code>HttpURLConnection</code> passed in
         *
         * @param conn		<code>HttpUrlConnection</code> which represents the 
	 *			URL request
         * @param u		<code>URL</code> of the URL request
         * @param authManager	the <code>AuthManager</code> containing all the 
         *			cookies for this <code>UrlConfig</code>
         */
	private void setConnectionAuthorization(HttpURLConnection conn, URL u, AuthManager authManager)
	{
		if (authManager != null)
		{
			String authHeader = authManager.getAuthHeaderForURL(u);
			if (authHeader != null)
			{
				conn.setRequestProperty("Authorization", authHeader);
			}
		}
	}

        /**
         * Get the response code of the URL connection and divide it by 100 thus
         * returning 2(for 2xx response codes), 3(for 3xx reponse codes), etc
         *
         * @param conn		<code>HttpURLConnection</code> of URL request
         * @param res		where all results of sampling will be stored
         * @param time		time when the URL request was first started
         * @return		HTTP response code divided by 100
         */
	private int getErrorLevel(HttpURLConnection conn, SampleResult res, long time)
	{
		int errorLevel = 2;
		try
		{
			errorLevel = ((HttpURLConnection) conn).getResponseCode() / 100;
		}
		catch (Exception e2)
		{
			res.putValue(this.TEXT_RESPONSE, e2.toString());
			res.setTime(System.currentTimeMillis() - time);
			res.putValue(SUCCESS, new Boolean(false));
		}
		return errorLevel;
	}

        /**
         * Follow redirection manually.  Normally if the web server does a redirection the
         * intermediate page is not returned.  Only the resultant page and the response code
         * for the page will be returned.  With redirection turned off, the response code of
         * 3xx will be returned together with a "Location" header-value pair to indicate
         * that the "Location" value needs to be followed to get the resultant page.
         *
         * @param conn		connection
         * @param u		
         * @exception MalformedURLException	if URL is not understood
         */
	private void redirectUrl(HttpURLConnection conn, URL u, UrlConfig urlConfig) throws MalformedURLException
	{
		String loc = conn.getHeaderField("Location");
		if (loc != null)
		{
			if (loc.indexOf("http") == -1)
			{
				String tempURL = u.toString();
				if (loc.startsWith("/"))
				{
					int ind = tempURL.indexOf("//") + 2;
					loc = tempURL.substring(0, tempURL.indexOf("/", ind) + 1) + loc.substring(1);
				}
				else
				{
					loc = u.toString().substring(0,
							u.toString().lastIndexOf('/') + 1) + loc;
				}
			}
		}
		URL newUrl = new URL(loc);
		urlConfig.putProperty(UrlConfig.DOMAIN, newUrl.getHost());
		urlConfig.putProperty(UrlConfig.PATH, newUrl.getFile());
	}

        /**
         * Samples <code>Entry</code> passed in and stores the result in 
         * <code>SampleResult</code>
         *
         * @param e		<code>Entry</code> to be sampled
         * @param redirected	whether redirection is turned on
         * @return		results of the sampling
         */
	private SampleResult sample(Entry e, boolean redirected)
	{
		long time;
		SampleResult res = new SampleResult();
		UrlConfig url = getUrlConfig(e);
		URL u = null;
		try
		{
			u = url.getUrl();
			res.putValue(Sampler.SAMPLE_LABEL, u.toString());
			System.out.println("Sampling url: " + u);
			HttpURLConnection conn = setupConnection(u, url, e);
			writeToStream(redirected, url, conn);
			time = System.currentTimeMillis();
			conn.connect();
			saveConnectionCookies(conn, u, (CookieManager) e.getConfigElement(CookieManager.class));

			int errorLevel = getErrorLevel(conn, res, time);
			if (errorLevel == 2)
			{
				String ret = readResponse(conn);
				time = System.currentTimeMillis() - time;
				res.putValue(this.TEXT_RESPONSE, ret);
				res.putValue(SUCCESS, new Boolean(true));
				getResponseHeaders(conn, res);
			}
			else if (errorLevel == 3)
			{
				redirectUrl(conn, u, url);

				time = System.currentTimeMillis() - time;
				res = sample(e, true);
				time += res.getTime();
			}
			else
			{
				// Could not sample the URL
				System.out.println("URL = " + u);
				throw new IOException(((HttpURLConnection) conn).getResponseMessage());
			}
			res.setTime(time);
			return res;
		}
		catch (IOException ex)
		{
			ex.printStackTrace();
			res.setTime((long) 0);
			res.putValue(this.TEXT_RESPONSE, ex.toString());
			res.putValue(SUCCESS, new Boolean(false));
		}
		return res;
	}

        /**
         * From the <code>HttpURLConnection</code>, store all the "set-cookie" key-pair
         * values in the cookieManager of the <code>UrlConfig</code>
         *
         * @param conn		<code>HttpUrlConnection</code> which represents the 
	 *			URL request
         * @param u		<code>URL</code> of the URL request
         * @param cookieManager	the <code>CookieManager</code> containing all the 
         *			cookies for this <code>UrlConfig</code>
         */
	private void saveConnectionCookies(HttpURLConnection conn, URL u, CookieManager cookieManager)
	{
		if (cookieManager != null)
		{
			for (int i = 1; conn.getHeaderFieldKey(i) != null; i++)
			{
				if (conn.getHeaderFieldKey(i).equalsIgnoreCase("set-cookie"))
				{
					cookieManager.addCookieFromHeader(conn.getHeaderField(i), u);
				}
			}
		}
	}

        /**
         * Reads the response from the URL connection
         *
         * @param conn			URL from which to read response
         * @return			response in <code>String</code>
         * @exception IOException	if an I/O exception occurs
         */
	private String readResponse(HttpURLConnection conn) throws IOException
	{
		byte[] buffer = new byte[4096];
		BufferedInputStream in = new BufferedInputStream(conn.getInputStream());
		java.io.ByteArrayOutputStream w = new ByteArrayOutputStream();
		int x = 0;
		while ((x = in.read(buffer)) != -1)
		{
			w.write(buffer, 0, x);
		}
		in.close();
		return w.toString();
	}

	static
	{
		if (!JMeterUtils.getPropDefault("ssl.provider", "none").equals("none"))
		{
			try
			{
				Class c = Class.forName("org.apache.jmeter.protocol.http.util.SSLStaticProvider");
				c.newInstance();
			}
			catch (Exception ex)
			{
				System.err.println("Could not find SSL Provider");
				ex.printStackTrace();
			}
		}
	}

	/**
	 *  Gets the ResponseHeaders from the URLConnection, save them to the SampleResults
	 *  object.
	 *
	 *@param  conn		connection from which the headers are read
	 *@param  res   	where the headers read are stored
	 */
	protected void getResponseHeaders(HttpURLConnection conn, SampleResult res)
	{
		HashMap hValues = new HashMap(20);
		for (int i = 1; conn.getHeaderFieldKey(i) != null; i++)
		{
			hValues.put(conn.getHeaderFieldKey(i), conn.getHeaderField(i));
		}
		res.putValue(Sampler.HEADER, hValues);
	}

}
