/*
 * Enhydra Java Application Server Project
 * 
 * The contents of this file are subject to the Enhydra Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License on
 * the Enhydra web site (http://www.enhydra.org/).
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 
 * the License for the specific terms governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Enhydra Application Server is Lutris
 * Technologies, Inc. The Enhydra Application Server and portions created
 * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
 * All Rights Reserved.
 * 
 * Contributor(s):
 * 
 * $Id: ContextServices.java,v 1.2 2001/09/25 19:05:30 cryd0221 Exp $
 */
package org.enhydra.barracuda.core.util.http;

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

import org.enhydra.barracuda.core.util.data.Base64;

/**
 * This class encapsulates access to/from a URL via
 * both POST and GET methods. To use, simply set the
 * URL, the method (POST/GET), and the params. If you're
 * using get, the params are optional (they can be included
 * as part of the URL). Also note that you can pass a
 * username and password if you need to do basic authentication
 *
 * Refer to the source for this class (main method) to see an example of
 * how you would use this class for both POST and GET methods:
 */
public class HttpRequester {

	public static final String POST = "POST";
	public static final String GET = "GET";

	protected URL url = null;
	protected String method = GET;
	protected Map props = null;
	protected HttpOutputWriter outputWriter = null;
    protected String user = null;
    protected String password = null;
    protected boolean authenticate = false;

	protected OutputStream outStream = null;
	protected InputStream inStream = null;
	protected BufferedReader in = null;

	/**
	 * Set the Request. This is a convenience method to encapsulate
	 * calls to setUrl, setMethod, and setParams all in one fell swoop.
	 *
	 * @param url the URL we wish to access
	 * @param method the method we wish to use (either GET or POST)
	 * @param props the Map contains our key-value URL parameter pairs.
	 *   	If the value is a Set, the resulting URL will contain a key-value
	 *		mapping for each entry in the Set.
	 * @throws MalformedURLException
	 */
	public void setRequest(String iurl, String imethod, Map iprops) throws MalformedURLException {
        setRequest(iurl, imethod, iprops, null);
	}

	/**
	 * Set the Request. This is a convenience method to encapsulate
	 * calls to setUrl, setMethod, and setParams all in one fell swoop.
	 *
	 * @param url the URL we wish to access
	 * @param method the method we wish to use (either GET or POST)
	 * @param props the Map contains our key-value URL parameter pairs.
	 *   	If the value is a Set, the resulting URL will contain a key-value
	 *		mapping for each entry in the Set.
	 * @throws MalformedURLException
	 */
	public void setRequest(URL iurl, String imethod, Map iprops) throws MalformedURLException {
        setRequest(iurl, imethod, iprops, null);
	}

	/**
	 * Set the Request. This is a convenience method to encapsulate
	 * calls to setUrl, setMethod, and setParams all in one fell swoop.
	 *
	 * @param url the URL we wish to access
	 * @param method the method we wish to use (either GET or POST)
	 * @param props the Map contains our key-value URL parameter pairs.
	 *   	If the value is a Set, the resulting URL will contain a key-value
	 *		mapping for each entry in the Set.
	 * @param outputWriter the HttpOutputWriter we wish to write to
	 * @throws MalformedURLException
	 */
	public void setRequest(String iurl, String imethod, Map iprops, HttpOutputWriter ioutputWriter) throws MalformedURLException {
        setRequest(iurl, imethod, iprops, null, null, null);
	}

	/**
	 * Set the Request. This is a convenience method to encapsulate
	 * calls to setUrl, setMethod, and setParams all in one fell swoop.
	 *
	 * @param url the URL we wish to access
	 * @param method the method we wish to use (either GET or POST)
	 * @param props the Map contains our key-value URL parameter pairs.
	 *   	If the value is a Set, the resulting URL will contain a key-value
	 *		mapping for each entry in the Set.
	 * @param outputWriter the HttpOutputWriter we wish to write to
	 * @throws MalformedURLException
	 */
	public void setRequest(URL iurl, String imethod, Map iprops, HttpOutputWriter ioutputWriter) throws MalformedURLException {
        setRequest(iurl, imethod, iprops, null, null, null);
	}

	/**
	 * Set the Request. This is a convenience method to encapsulate
	 * calls to setUrl, setMethod, and setParams all in one fell swoop.
	 *
	 * @param url the URL we wish to access
	 * @param method the method we wish to use (either GET or POST)
	 * @param props the Map contains our key-value URL parameter pairs.
	 *   	If the value is a Set, the resulting URL will contain a key-value
	 *		mapping for each entry in the Set.
     * @param user the user named required to connect
     * @param password the password named required to connect
	 * @param outputWriter the HttpOutputWriter we wish to write to
	 * @throws MalformedURLException
	 */
	public void setRequest(String iurl, String imethod, Map iprops, String iuser, String ipwd, HttpOutputWriter ioutputWriter) throws MalformedURLException {
		if (iurl!=null) setUrl(iurl);
		if (imethod!=null) setMethod(imethod);
		if (iprops!=null) setParams(iprops);
        if (iuser!=null) setUser(iuser);
        if (ipwd!=null) setPassword(ipwd);
		if (ioutputWriter!=null) setOutputWriter(ioutputWriter);
	}

	/**
	 * Set the Request. This is a convenience method to encapsulate
	 * calls to setUrl, setMethod, and setParams all in one fell swoop.
	 *
	 * @param url the URL we wish to access
	 * @param method the method we wish to use (either GET or POST)
	 * @param props the Map contains our key-value URL parameter pairs.
	 *   	If the value is a Set, the resulting URL will contain a key-value
	 *		mapping for each entry in the Set.
     * @param user the user named required to connect
     * @param password the password named required to connect
	 * @param outputWriter the HttpOutputWriter we wish to write to
	 * @throws MalformedURLException
	 */
	public void setRequest (URL iurl, String imethod, Map iprops, String iuser, String ipwd, HttpOutputWriter ioutputWriter) throws MalformedURLException {
		if (iurl!=null) setUrl(iurl);
		if (imethod!=null) setMethod(imethod);
		if (iprops!=null) setParams(iprops);
        if (iuser!=null) setUser(iuser);
        if (ipwd!=null) setPassword(ipwd);
		if (ioutputWriter!=null) setOutputWriter(ioutputWriter);
	}

	/**
	 * Set the URL we wish to access
	 *
	 * @param url the URL we wish to access
	 * @throws MalformedURLException
	 */
	public void setUrl (String iurl) throws MalformedURLException {
		//if we're setting it back to null, otherwise, create the url 
		//which represents the servlet which will do the generation
		if (iurl==null) url = null;
		else setUrl (new URL (iurl));
	}

	/**
	 * Set the URL we wish to access
	 *
	 * @param url the URL we wish to access
	 */
	public void setUrl (URL iurl) {
		url = iurl;
	}

	/**
	 * Get the URL for the HttpRequest object
	 *
	 * @return the URL behind this request
	 */
	public URL getUrl () {
		return url;
	}

	/**
	 * Set the method we wish to use. Valid values are either GET
	 * or POST. Default is GET.
	 *
	 * @param  method the method we wish to use (either GET or POST)
	 */
	public void setMethod (String imethod) {
		if (imethod.toUpperCase().equals(POST)) method = POST;
		else method = GET;
	}

	/**
	 * Get the method we're using for this HttpRequest object
	 *
	 * @return the method we're using (either GET or POST)
	 */
	public String getMethod () {
		return method;
	}

	/**
	 * Set the parmeters we wish to pass to the URL as name-value pairs.
	 * If you are using the POST method, it will look for properties in
	 * here. If you are using the get method, you can manually pass the
	 * properties as part of the URL string, and just ignore this method.
	 *
	 * @param  props the Map contains our key-value URL parameter pairs.
	 *   	If the value is a Set, the resulting URL will contain a key-value
	 *		mapping for each entry in the Set.
	 */
	public void setParams (Map iprops) {
		props = iprops;
	}

	/**
	 * Return the HashMap object for this HttpRequest. If the map is null (ie.
	 * because you are using the GET method), we attempt to look for the
	 * properties in the actual URL string and build a HashMap from that.
	 *
	 * @return a Map containing all the parameters for this HttpRequest
	 */
	public Map getParams () {
		//if someone asks for the param map and its null, try
		//and build it based on the actual URL string
		if (props==null) {
			//avoid the obvious errs
			if (url==null) return null;

			//build the HashMap
			props = HttpConverter.cvtURLStringToMap (url.toString(), "&");
		}

		//return the HashMap
		return props;
	}

    /**
     * Set the user (if we need to authenticate in order to make the connection)
     *
     * @param user the user name
     */
    public void setUser(String iuser) {
        user = iuser;
        authenticate = (user!=null);
    }

    /**
     * Get the user name
     *
     * @return the user name
     */
    public String getUser() {
        return user;
    }

    /**
     * Set the password (if we need to authenticate in order to make the connection)
     *
     * @param password the password
     */
    public void setPassword(String ipassword) {
        password = ipassword;
        authenticate = (password!=null);
    }

    /**
     * Get the password
     *
     * @return the password
     */
    protected String getPassword() {
        return password;
    }

	/**
	 * Set the output writer to be used for posting data
	 *
	 * @param  ioutputWriter the HttpOutputWriter
	 */
	public void setOutputWriter (HttpOutputWriter ioutputWriter) {
		outputWriter = ioutputWriter;
	}

	/**
	 * Return the output writer. If none is set, the default will be used.
	 *
	 * @return the HttpOutputWriter
	 */
	public HttpOutputWriter getOutputWriter () {
		//if someone asks for the output writer and its null,
		//return the default
		if (outputWriter==null) {
			return new DefaultOutputWriter ();
		} else
			return outputWriter;
	}

	/**
	 * Connect to the URL
	 *
	 * @throws ConnectException
	 * @throws IOException
	 */
	public void connect() throws ConnectException, IOException {
		//pre-launch checks
		if (url==null) throw new ConnectException ("Invalid URL. URL can not be NULL");
		if (method!=POST && method!=GET) throw new ConnectException ("Invalid Method. Method must be either POST or GET");

		//POST
		if (method==POST) {
		    //open the connection and set output to true
			URLConnection conn = url.openConnection();
			conn.setDoOutput(true);

            //Set up an authorization header with our credentials (this chunk of
            //code stolen from org.apache.catalina.ant.AbstractCatalinaTask; 
            //thanks to Craig R. McClanahan [craigmcc@apache.org] for pointing
            //me to this example)
            if (authenticate) {
                String input = user + ":" + password;
                String output = new String(Base64.encode(input.getBytes()));
                conn.setRequestProperty("Authorization", "Basic " + output);
            }
            
			//write the key values to the stream
			outStream = conn.getOutputStream();
			getOutputWriter().writeOutput(outStream);

			//now open an input stream to read the response
			inStream = conn.getInputStream();
			in = new BufferedReader(new InputStreamReader(inStream));
		//GET
		} else {
			//first see if we have a param structure...if so, build a URL String.
			if (props!=null) {
				//figure out what the current URL is and strip off any parameters
				String newUrl = getUrl().toString();
				int pos = newUrl.indexOf("?");
				if (pos>0) newUrl = newUrl.substring(0,pos);

				//now run through the map and build a new url string
				setUrl(newUrl+"?"+ HttpConverter.cvtMapToURLString(props, "&"));
                newUrl = getUrl().toString();
				if (newUrl.endsWith("?")) setUrl(newUrl.substring(0,newUrl.length()-1));

				//now set the url and set the params object back to null so we don't
				//need to rebuild the URL string again
				setParams(null);
			}

			//open the connection and set output to true
			URLConnection conn = url.openConnection();
			conn.setDoInput(true);
            
            //Set up an authorization header with our credentials (this chunk of
            //code stolen from org.apache.catalina.ant.AbstractCatalinaTask; 
            //thanks to Craig R. McClanahan [craigmcc@apache.org] for pointing
            //me to this example)
            if (authenticate) {
                String input = user + ":" + password;
                String output = new String(Base64.encode(input.getBytes()));
                conn.setRequestProperty("Authorization", "Basic " + output);
            }
            
			//now get an input stream
			inStream = conn.getInputStream();
			in = new BufferedReader(new InputStreamReader(inStream));
		}
	}

	/**
	 * Read responses from the URL
	 *
	 * @return a String representation of what we got back
	 * @throws ConnectException
	 * @throws IOException
	 */
	public String readLine() throws ConnectException, IOException {
		//pre-launch checks
		if (in==null) throw new ConnectException ("Connection is not active");

		//get the line
		String inputLine = in.readLine();

		//if its null, auto disconnect
		if (inputLine==null) disconnect();

		//now return the String
		return inputLine;
	}

	/**
	 * Get the underlying output stream
	 *
	 * @return the output stream
	 * @throws ConnectException
	 */
	public OutputStream getOutputStream() throws ConnectException {
		//pre-launch checks
		if (outStream==null) throw new ConnectException ("Connection is not active");

		//return the input stream
		return outStream;
	}

	/**
	 * Get the underlying input stream
	 *
	 * @return the input stream
	 * @throws ConnectException
	 */
	public InputStream getInputStream() throws ConnectException {
		//pre-launch checks
		if (inStream==null) throw new ConnectException ("Connection is not active");

		//return the input stream
		return inStream;
	}

	/**
	 * Disconnect from the URL. You really only need to call this
	 * if you terminate the readLine process on your end. if
	 * readLine() encounters a null value, it assumes input is
	 * complete and automatically calls this method.
	 */
	public void disconnect() {
		if (outStream!=null) try {outStream.close();} catch (IOException ioe) {}
		outStream = null;
		if (in!=null) try {in.close();} catch (IOException ioe) {}
		in = null;
		if (inStream!=null) try {inStream.close();} catch (IOException ioe) {}
		inStream = null;
	}

	/**
	 * This inner class provides the default mechanism to write to an output stream
	 */
	class DefaultOutputWriter implements HttpOutputWriter {
		public void writeOutput(OutputStream outputStream) throws IOException {
			System.out.println ("Using default HttpOutputWriter to POST data");
			PrintWriter out = new PrintWriter(outputStream);
			try {
				String paramStr = HttpConverter.cvtMapToURLString(props, "&");
				if (paramStr!=null && paramStr.trim().length()>0) out.print (paramStr);
				System.out.println ("Data posted!");
			} finally {
				out.close();
				outputStream.close();
			}
		}
	}


	public static void main(String[] args) {
		//sample GET
		try {
			HttpRequester hr = new HttpRequester();
//			String urlStr = "http://localhost:8080/manager/list";   //connect to Tomcat manager app
//			String paramStr = "";
			String urlStr = "http://localhost:8080/manager/reload?path=/examples";   //connect to Tomcat manager app
			String paramStr = null;
			Map props = null;
            if (paramStr!=null) HttpConverter.cvtURLStringToMap (paramStr, "&");
			hr.setRequest(urlStr, HttpRequester.GET, props, "admin", "123123", null);
			hr.connect();
			String inputLine;
			while ((inputLine = hr.readLine()) != null) {
				System.out.println(inputLine);
			}
			hr.disconnect();
		} catch (Exception e) {
			e.printStackTrace();
		}	
/*		
		//sample POST
		try {
			HttpRequester hr = new HttpRequester();
			String urlStr = "http://localhost:8010/EventHandler6/rocks.examples.ex1.TestEvent.event";
			String paramStr = "parm1=foo&parm2=blah&parm2=boo";
			Map props = HttpConverter.cvtURLStringToMap (paramStr, "&");
			hr.setRequest (urlStr, HttpRequester.POST, props);
			hr.connect();
			String inputLine;
			while ((inputLine = hr.readLine()) != null) {
				System.out.println(inputLine);
			}
			hr.disconnect();
		} catch (Exception e) {
			e.printStackTrace();
		}	
*/
	}
}
