/*
 * ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in
 * the documentation and/or other materials provided with the
 * distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 * if any, must include the following acknowledgment:
 * "This product includes software developed by the
 * Apache Software Foundation (http://www.apache.org/)."
 * Alternately, this acknowledgment may appear in the software itself,
 * if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 * "Apache JMeter" must not be used to endorse or promote products
 * derived from this software without prior written permission. For
 * written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 * "Apache JMeter", nor may "Apache" appear in their name, without
 * prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package com.hin.jmeter.protocol.http.sampler;

import org.apache.jmeter.protocol.http.sampler.HTTPSampler;
import org.apache.jmeter.protocol.http.config.*;
import org.apache.jmeter.samplers.*;

import java.util.*;
import java.net.*;

import org.apache.log4j.*;

import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.framework.TestResult;

// required to use to tidy parser
import java.io.*;
import org.w3c.dom.*;
import org.w3c.tidy.Tidy;
import org.xml.sax.SAXException;

// required for TestCase
import com.hin.jmeter.protocol.http.config.UrlConfigFull;
/**
 * A sampler that downloads all downloadable components e.g. images,
 * applets etc.  
 *
 * @author	Khor Soon Hin
 * @version	1.0
 * @created	$Date: 2001/07/17 $
 */
public class HTTPSamplerFull extends HTTPSampler
{
  public final static String RESULT_LIST = "httpSamplerFull.RESULT_LIST";

  protected static Category catClass = 
    Category.getInstance(HTTPSamplerFull.class.getName());

  protected static String utfEncodingName;

  protected static String HTTP = "http://";

  protected URL baseUrl;

  public HTTPSamplerFull()
  {
    super();
    catClass.debug("Start : HTTPSamplerFull1");
    catClass.debug("End : HTTPSamplerFull1");
  }

  /**
   *  Gets the UrlConfig attribute of the HTTPSampler object
   *
   * @param  e  Description of Parameter
   * @return    The UrlConfig value
   */
  public UrlConfig getUrlConfig(Entry e)
  {
    UrlConfig urlConfig = (UrlConfig) 
      e.getConfigElement(com.hin.jmeter.protocol.http.config.UrlConfigFull.class);
    if(catClass.isDebugEnabled())
    {
      catClass.debug("getUrlConfig1 : Returning urlConfig - " + urlConfig.toString());
    }
    return urlConfig;
  }

  /**
   * Samples the <code>Entry</code> passed in and stores the result in 
   * <code>SampleResult</code>
   *
   * @param entry	an entry to be sampled
   * @param redirected  whether redirection is turned on
   * @return	resuls of the sampling
   */
  public SampleResult sample(Entry e)
  {
    catClass.debug("Start : sample2");
    SampleResult res = super.sample(e);
    String displayName = (String)res.getValue(Sampler.SAMPLE_LABEL);
    res.putValue(SampleResult.DISPLAY_NAME, displayName);
    List loadBinaryList = new ArrayList();
    if(catClass.isDebugEnabled())
    {
      catClass.debug("sample2 : main page loading time - " + res.getTime());
    }
    Document html = null;
    try
    {
      baseUrl = new URL((String)res.getValue(Sampler.SAMPLE_LABEL));
      if(catClass.isDebugEnabled())
      {
        catClass.debug("sample2 : baseUrl - " + baseUrl.toString());
      }
      // now use parser to look for image tags
      html = (Document)getDOM((String)res.getValue(Sampler.TEXT_RESPONSE));
    }
    catch(SAXException exception)
    {
      catClass.error("sample2 : Error parsing document - " + exception);
      catClass.error("sample2 : Setting SampleResult SUCCESS value to false");
      res.putValue(SUCCESS, new Boolean(false));
    }
    catch(MalformedURLException exception)
    {
      catClass.error("sample2 : Error creating URL - " + exception);
      catClass.error("sample2 : Setting SampleResult SUCCESS value to false");
      res.putValue(SUCCESS, new Boolean(false));
    }
    try
    {
      NodeList nodeList = html.getElementsByTagName("img");
      for(int i = 0; i < nodeList.getLength(); i++)
      {
        Node tempNode = nodeList.item(i);
        if(catClass.isDebugEnabled())
        {
          catClass.debug("sample2 : Image tags - " + tempNode);
        }
        // get the url of those images
        NamedNodeMap nnm = tempNode.getAttributes();
        Node namedItem = nnm.getNamedItem("src");
        String imgUrlStr = namedItem.getNodeValue();
        // download those images
        URL imgUrl = new URL(baseUrl, imgUrlStr);
        if(catClass.isDebugEnabled())
        {
          catClass.debug("sample2 : Image url without baseUrl - " + imgUrlStr);
          catClass.debug("sample2 : Image url with baseUrl - " + imgUrl);
        }
        SampleResult imgRes = new SampleResult();
        imgRes.putValue(SampleResult.DISPLAY_NAME, imgUrl.toString());
        imgRes.putValue(Sampler.TEXT_RESPONSE, loadBinary(imgUrl, e, imgRes));
        loadBinaryList.add(imgRes);
      }
    }
    catch(IOException exception)
    {
      catClass.error("sample2 : Error reading from URL - " + exception);
      catClass.error("sample2 : Setting SampleResult SUCCESS value to false");
      res.putValue(SUCCESS, new Boolean(false));
    }
    try
    {
      // use parser to look for applet tags
      NodeList nodeList = html.getElementsByTagName("applet");
      for(int i = 0; i < nodeList.getLength(); i++)
      {
        Node tempNode = nodeList.item(i);
        if(catClass.isDebugEnabled())
        {
          catClass.debug("sample2 : Applet tags - " + tempNode);
        }
        // get the url of those applets
        NamedNodeMap nnm = tempNode.getAttributes();
        Node namedItem = nnm.getNamedItem("code");
        String appletUrlStr = namedItem.getNodeValue() + ".class";
        // download those applet
        URL appletUrl = new URL(baseUrl, appletUrlStr);
        if(catClass.isDebugEnabled())
        {
          catClass.debug("sample2 : Applet url without baseUrl - " + appletUrlStr);
          catClass.debug("sample2 : Applet url with baseUrl - " + appletUrl);
        }
        SampleResult appRes = new SampleResult();
        appRes.putValue(SampleResult.DISPLAY_NAME, appletUrl.toString());
        appRes.putValue(Sampler.TEXT_RESPONSE, loadBinary(appletUrl, e, appRes));
        loadBinaryList.add(appRes);
      }
    }
    catch(IOException exception)
    {
      catClass.error("sample2 : Error reading from URL - " + exception);
      catClass.error("sample2 : Setting SampleResult SUCCESS value to false");
      res.putValue(SUCCESS, new Boolean(false));
    }
    try
    {
      // use parser to look for input tags with image types as well
      NodeList nodeList = html.getElementsByTagName("input");
      for(int i = 0; i < nodeList.getLength(); i++)
      {
        Node tempNode = nodeList.item(i);
        if(catClass.isDebugEnabled())
        {
          catClass.debug("sample2 : Input tags - " + tempNode);
        }
        // get the url of those images
        NamedNodeMap nnm = tempNode.getAttributes();
        Node namedItem = nnm.getNamedItem("type");
        String inputType = namedItem.getNodeValue();
        if(catClass.isDebugEnabled())
        {
          catClass.debug("sample2 : Input type - " + inputType);
        }
        if(inputType != null && inputType.equalsIgnoreCase("image"))
        {
          namedItem = nnm.getNamedItem("src");
          String imgUrlStr = namedItem.getNodeValue();
          // download those images
          URL imgUrl = new URL(baseUrl, imgUrlStr);
          if(catClass.isDebugEnabled())
          {
            catClass.debug("sample2 : Image url without baseUrl - " + imgUrlStr);
            catClass.debug("sample2 : Image url with baseUrl - " + imgUrl);
          }
          SampleResult imgRes = new SampleResult();
          imgRes.putValue(Sampler.TEXT_RESPONSE, loadBinary(imgUrl, e, imgRes));
          imgRes.putValue(SampleResult.DISPLAY_NAME, imgUrl.toString());
          loadBinaryList.add(imgRes);
        }
      }
      res.putValue(RESULT_LIST, loadBinaryList);
    }
    catch(IOException exception)
    {
      catClass.error("sample2 : Error reading from URL - " + exception);
      catClass.error("sample2 : Setting SampleResult SUCCESS value to false");
      res.putValue(SUCCESS, new Boolean(false));
    }
    catClass.debug("End : sample2");
    return res;
  }

  /**
   * Download the binaries from given <code>URL</code>
   *
   * @param url		<code>URL</code> from where binary is to be downloaded
   * @param e     	<code>Entry</code> being sampled
   * @param res		<code>SampleResult</code> to store sampling results
   * @return	binary downloaded
   */
  protected String loadBinary(URL url, Entry e, SampleResult res) 
    throws IOException
  {
    catClass.debug("Start : loadBinary1");
    String ret = null;
    // Create a dummy UrlConfig and set only the method.  Set the method to
    // GET.  Pass this UrlConfig to setupConnection() 'cos it uses UrlConfig
    // to determine the HTTP method to get the url.  In this case, we are
    // loading binaries so the method has to be GET.
    UrlConfig urlConfig = new UrlConfig();
    urlConfig.setMethod(UrlConfig.GET);
    HttpURLConnection conn = setupConnection(url, urlConfig, e);
    conn.connect();
    long time = System.currentTimeMillis();
    int errorLevel = getErrorLevel(conn, res);
    if (errorLevel == 2)
    {
      ret = readResponse(conn);
      res.putValue(SUCCESS, new Boolean(true));
      res.setTime(System.currentTimeMillis() - time);
    }
    else
    {
      res.putValue(SUCCESS, new Boolean(false));
      int responseCode = ((HttpURLConnection)conn).getResponseCode();
      String responseMessage = ((HttpURLConnection)conn).getResponseMessage();
      catClass.error("loadBinary1 : failed response code - " +
        responseCode);
      catClass.error("loadBinary1 : failed response message - " + 
        responseMessage);
      throw new IOException(responseMessage);
    }
    if(catClass.isDebugEnabled())
    {
      catClass.debug("loadBinary1 : binary - " + ret.substring(0,5));
      catClass.debug("loadBinary1 : loadTime - " + res.getTime());
    }
    catClass.debug("End : loadBinary1");
    return ret;
  }

  /**
   * 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
   * @return              HTTP response code divided by 100
   */
  protected int getErrorLevel(HttpURLConnection conn, SampleResult res)        
  {
    catClass.debug("Start : getErrorLevel1");
    int errorLevel = 2;
    try
    {
      int responseCode = ((HttpURLConnection) conn).getResponseCode();
      String responseMessage = ((HttpURLConnection) conn).getResponseMessage();
      errorLevel = responseCode/100;
      if(catClass.isDebugEnabled())
      {
        catClass.debug("getErrorLevel1 : responseCode - " + responseCode);
        catClass.debug("getErrorLevel1 : responseMessage - " + 
          responseMessage);
      }
    }
    catch (Exception e2)
    {
      catClass.error("getErrorLevel1 : " + 
        "Error getting response code for HttpUrlConnection -  + e2");
      catClass.error("getErrorLevel1 : " + 
        "Setting SampleResult SUCCESS value to false");
      res.putValue(this.TEXT_RESPONSE, e2.toString());
      res.putValue(SUCCESS, new Boolean(false));
    }
    catClass.debug("End : getErrorLevel1");
    return errorLevel;
  }

  /**
   * Returns <code>tidy</code> as HTML parser
   *
   * @return	a <code>tidy</code> HTML parser
   */
  protected static Tidy getParser()
  {
    catClass.debug("Start : getParser1");
    Tidy tidy = new Tidy();
    tidy.setCharEncoding(org.w3c.tidy.Configuration.UTF8);
    tidy.setQuiet(true);
    if(catClass.isDebugEnabled())
    {
      catClass.debug("getParser1 : tidy parser created - " + tidy);
    }
    catClass.debug("End : getParser1");
    return tidy;
  }

  /**
   * Returns a node representing a whole xml given an xml document
   *
   * @param text	an xml document
   * @return	a node representing a whole xml
   */
  protected static Node getDOM(String text) throws SAXException
  {
    catClass.debug("Start : getDOM1");
    try
    {
      Node node = getParser().parseDOM(new 
        ByteArrayInputStream(text.getBytes(getUTFEncodingName())), null);
      if(catClass.isDebugEnabled())
      {
        catClass.debug("node : " + node);
      }
      catClass.debug("End : getDOM1");
      return node;
    }
    catch(UnsupportedEncodingException e)
    {
      catClass.error("getDOM1 : Unsupported encoding exception - " + e);
      catClass.debug("End : getDOM1");
      throw new RuntimeException("UTF-8 encoding failed");
    }
  }

  /**
   * Returns the encoding type which is different for different jdks
   * even though the mean the same thing i.e. UTF8 or UTF-8
   *
   * @return	either UTF8 or UTF-8 depending on the jdk version
   */
  protected static String getUTFEncodingName()
  {
    catClass.debug("Start : getUTFEncodingName1");
    if (utfEncodingName == null)
    {
      String versionNum = System.getProperty( "java.version" );
      if(catClass.isDebugEnabled())
      {
        catClass.debug("getUTFEncodingName1 : versionNum - " + versionNum);
      }
      if (versionNum.startsWith( "1.1" ))
      {
         utfEncodingName = "UTF8";
      }
      else 
      {
        utfEncodingName = "UTF-8";
      }
    }
    if(catClass.isDebugEnabled())
    {
      catClass.debug("getUTFEncodingName1 : Returning utfEncodingName - " + 
        utfEncodingName);
    }
    catClass.debug("End : getUTFEncodingName1");
    return utfEncodingName;
  }

  public static class Test extends TestCase
  {
    private HTTPSamplerFull hsf;
    private Entry entry;

    private static Category catClass = 
      Category.getInstance(Test.class.getName());
 
    public Test(String name)
    {
      super(name);
    }

    protected void setUp()
    {
      catClass.debug("Start : setUp1");
      hsf = new HTTPSamplerFull();
      entry = new Entry();
      UrlConfigFull urlConfigFull = new UrlConfigFull();
      urlConfigFull.setMethod(UrlConfig.GET);
      urlConfigFull.setProtocol("file");
//      urlConfigFull.setPort(8080);
//      urlConfigFull.setDomain("jakarta.apache.org");
      urlConfigFull.setPath("HTTPSamplerFullTestFile.txt");
      entry.setSamplerClass(HTTPSamplerFull.class);
      entry.addConfigElement(urlConfigFull);
      catClass.debug("End : setUp1");
    }

    public void testGetUTFEncodingName()
    {
      catClass.debug("Start : testGetUTFEncodingName1");
      String javaVersion = System.getProperty("java.version");
      System.setProperty("java.version", "1.1");
      assertEquals("UTF8", HTTPSamplerFull.getUTFEncodingName());
      // need to clear utfEncodingName variable first 'cos
      // getUTFEncodingName checks to see if it's null
      utfEncodingName = null;
      System.setProperty("java.version", "1.2");
      assertEquals("UTF-8", HTTPSamplerFull.getUTFEncodingName());
      System.setProperty("java.version", javaVersion);
      catClass.debug("End : testGetUTFEncodingName1");
    }

    public void testGetUrlConfig()
    {
      catClass.debug("Start : testGetUrlConfig1");
      UrlConfig urlConfig = hsf.getUrlConfig(entry);
      assertEquals(UrlConfig.GET, urlConfig.getMethod());
      assertEquals("file", urlConfig.getProtocol());
//      assertEquals(8080, urlConfig.getPort());
//      assertEquals("jakarta.apache.org", urlConfig.getDomain());
      assertEquals("HTTPSamplerFullTestFile.txt", urlConfig.getPath());
      catClass.debug("End : testGetUrlConfig1");
    }
    // Can't think of a self-contained way to test this 'cos it requires
    // http server.  Tried to use file:// but HTTPSampler's sample 
    // specifically requires http.
    public void testSampleMain()
    {
      catClass.debug("Start : testSampleMain1");
      // !ToDo : Have to wait till the day SampleResult is extended to 
      // store results of all downloaded stuff e.g. images, applets etc
      String fileInput = "<html>\n\n" +
        "<title>\n" +
        "  A simple applet\n" +
        "</title>\n" +
        "<body background=\"back.jpg\" vlink=\"#dd0000\" link=\"#0000ff\">\n" +
        "<center>\n" +
        "<h2>   A simple applet\n" +
        "</h2>\n" +
        "<br>\n" +
        "<br>\n" +
        "<table>\n" +
        "<td width = 20>\n" +
        "<td width = 500 align = left>\n" +
        "<img src=\"/tomcat.gif\">\n" +
        "<a href=\"Time.java\"> Read my code <a>\n" +
        "<p><applet code=Time width = 400 height = 200>\n" +
        "</applet>\n" +
        "</table>\n" +
        "<form>\n" +
        "  <input type=\"image\" src=\"/tomcat-power.gif\">\n" +
        "</form>\n" +
        "</body>\n" +
        "</html>\n";
      byte[] bytes = fileInput.getBytes();
      try
      {
        FileOutputStream fos = 
          new FileOutputStream("HTTPSamplerFullTestFile.txt");
        fos.write(bytes);
        fos.close();
      }
      catch(IOException e)
      {
        fail("Cannot create HTTPSamplerFullTestFile.txt in current directory " +
          "for testing - " + e);
      }
      // !ToDo
      // hsf.sample(entry);
      assertNull("Cannot think of way to test sample", null);
      catClass.debug("End : testSampleMain1");
    }

    protected void tearDown()
    {
      catClass.debug("Start : tearDown1");
      hsf = null;
      entry = null;
      catClass.debug("End : tearDown1");
    }

    public static void main(String[] args)
    {
      BasicConfigurator.configure();
      TestSuite suite = new TestSuite(Test.class);
      junit.textui.TestRunner runner = new junit.textui.TestRunner();
      runner.run(suite);
    }
  }
}

