/*
 * Created on 01.10.2004
 *
 * @author rschamberger
 * $ID$
 */
package at.gv.egovernment.moa.id.util;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequestWrapper;
import at.gv.egovernment.moa.logging.Logger;
import at.gv.egovernment.moa.util.URLDecoder;
/**
 * Special ServletRequestWrapper class which provides a more precise implementation of the getParameter* 
 * family. This implementation cares about the order of the parameters from Query String and HTTP POST 
 * Body. Use this as Filter class for Servlets which such needs.
 * 
 * @author Rudolf Schamberger
 * @version $Id$
 */
public class InOrderServletRequestWrapper extends HttpServletRequestWrapper {
  
  /**
   * standard encoding used to decode the URL string.
   */
  //
  public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
  /**
   * Vector that stores the order of the query paramters
   */
  private Vector queryParamOrder;
  
  /**
   * Hashtable that stores the content of the query paramters
   */
  private Hashtable queryParameters;
  
  /**
   * Vector that stores the order of the HTTP body paramters
   */
  private Vector bodyParamOrder;
  
  /**
   * Hashtable that stores the content of the HTTP body paramters
   */
  private Hashtable bodyParameters;
  
  /**
   * ServletContext
   */
  private ServletContext context;
  
  /**
   * Identifier used to identify query parameters
   */
  public static final int QUERY_PARAM = 1;
 
  /**
   * Identifier used to identify HTTP body parameters
   */
  public static final int BODY_PARAM = 2;	
  
  /**
   * @see HttpServletRequestWrapper
   */
  public InOrderServletRequestWrapper(final HttpServletRequest request, final ServletContext sContext) {
    super(request);
    this.context = sContext;
  }
  
  /**
   * parses the Query and if availlable also HTTP POST parameters
   * 
   * @param req a HttpServletRequest which should be parsed
   */
  protected final void parseParameters(final HttpServletRequest req)
  {
    queryParamOrder = new Vector();
    queryParameters = new Hashtable();
    bodyParamOrder = new Vector();
    bodyParameters = new Hashtable();
    //Insert code for Query string parsing
    String rawQuery = req.getQueryString();
    queryParameters = tokenize(queryParameters, queryParamOrder, rawQuery, DEFAULT_CHARACTER_ENCODING, true);
    //analyze HTTP Post body parameters
    if (req.getMethod().equalsIgnoreCase("POST"))
    {
      //get body encoding
      String enc = req.getCharacterEncoding();
      if (enc == null) enc = DEFAULT_CHARACTER_ENCODING;
      if (req.getContentType().equals("application/x-www-form-urlencoded"))
      {
        try
        {
          bodyParameters = parsePostData(bodyParameters, req.getContentLength(), req.getInputStream(), enc);
        }
        catch (IOException e)
        {
          context.log("could not open input stream of reqest \n" + e.toString());
        }
      }
      else
      {
        //TODO add multipart code
        context.log(
          "ERROR other Content-Types than 'application/x-www-form-urlencoded' not supported!");
      }
    }// end POST
  }
  /**
   * parses the HTTP POST parameters
   * 
   * @param ht parameter Hashtable to put parameters in.
   * @param length of content
   * @param instream the ServletInputStream of the request
   * @param encoding encoding of the instream
   * 
   * @return the Hashtable with the parsed data
   */
  private Hashtable parsePostData(Hashtable ht, final int length, final ServletInputStream instream, 
    final String encoding)
  {
    int inputLen, offset;
    byte[] postedBytes = null;
    boolean dataRemaining = true;
    String postedBody;
    StringBuffer sb = new StringBuffer();
    if (length <= 0)
    {
      return null;
    }
    postedBytes = new byte[length];
    try
    {
      offset = 0;
      while (dataRemaining)
      {
        inputLen = instream.read(postedBytes, offset, length - offset);
        if (inputLen <= 0)
        {
          throw new IOException("read error during reading the HTTP POST body");
        }
        offset += inputLen;
        if ((length - offset) == 0)
        {
          dataRemaining = false;
        }
      }
    }
    catch (IOException e)
    {
      System.out.println("Exception =" + e);
      return null;
    }
    postedBody = new String(postedBytes);
    Hashtable ht2 = tokenize(ht, bodyParamOrder, postedBody, encoding, false);
    return ht2;
  }
  /**
   * tokenizes parameter strings
   * 
   * @param ht parameter Hashtable to put parameters in.
   * @param order Vector in which the order of the tokenized parameters will be stored.
   * @param parameterString String to tokenize.
   * @param encoding which will be used to decode the parameterString.
   * 
   * @return the Hashtable with the parsed data
   */
  private Hashtable tokenize(Hashtable ht, Vector order, final String parameterString, final String encoding, boolean decode)
  {
    String[] valArray  = null;
    if (null == parameterString) return ht;
    StringTokenizer st = new StringTokenizer(parameterString, "&");
    String key = null;
    String val = null;
    while (st.hasMoreTokens())
    {
      String pair = (String) st.nextToken();
      int pos = pair.indexOf('=');
      if (pos == -1)
      {
        throw new IllegalArgumentException();
      }
      try
      {
      	if (decode) {
          	key = URLDecoder.decode(pair.substring(0, pos), encoding);
            val = URLDecoder.decode(pair.substring(pos + 1, pair.length()), encoding);
      	} else {
          	key = pair.substring(0, pos);
            val = pair.substring(pos + 1, pair.length());
      	}
        //Logger.debug("(" + Integer.toString(key.length()) + "=" + Integer.toString(pair.substring(0, pos).length()) + ")"+key+"|--|"+pair.substring(0, pos));
        //Logger.debug("(" + Integer.toString(val.length()) + "=" + Integer.toString(pair.substring(pos + 1, pair.length()).length()) + ")"+val+"|--|"+pair.substring(pos + 1, pair.length()));
      }
      catch (Exception e)
      {
        throw new IllegalArgumentException();
      }
      if (ht.containsKey(key))
      {
        String oldVals[] = (String[]) ht.get(key);
        valArray = new String[oldVals.length + 1];
        for (int i = 0; i < oldVals.length; i++)
        {
          valArray[i] = oldVals[i];
        }
        valArray[oldVals.length] = val;
      }
      else
      {
        valArray = new String[1];
        valArray[0] = val;
      }
      ht.put(key, valArray);
      order.addElement(key);
    }
    return ht;
  }
  
  /**
   * Returns the value of a request parameter as a String, or null if the
   * parameter does not exist. Request parameters are extra information sent with the request. For HTTP
   * servlets, parameters are contained in the query string or posted form data.
   * 
   * 
* You should only use this method when you are sure the parameter has only one value. If the parameter * might have more than one value, use {@link #getParameterValues}. * *
   * If you use this method with a multivalued parameter, the value returned is equal to the first value in
   * the array returned by getParameterValues.
   * 
   * 
   * If the parameter data was sent in the request body, such as occurs with an HTTP POST request, then
   * reading the body directly via {@link#getInputStream} or {@link #getReader}can interfere with the
   * execution of this method.
   * 
   * @param name a String containing the name of the parameter whose value is requested
   *
   * @return a String representing the single value of the parameter
   * 
   * @see #getParameterValues
   *  
   */
	public final String getParameter(final String name) {
	  String val = getParameter(name, QUERY_PARAM);
	  return (null != val) ? val : getParameter(name, BODY_PARAM);
	}
	/**
  * Returns the value of a request parameter as a String, or null if the
  * parameter does not exist. 
  * 
  * @param name a String containing the name of the parameter whose value is requested
  * @param parameterType type of parameter 
  * @see at.gv.egovernment.moa.id.util.ParametersInOrderServlet#QUERY_PARAM 
  * and @see at.gv.egovernment.moa.id.util.ParametersInOrderServlet#BODY_PARAM
  * @see getParameterValues(String name);
  * @return value of the (single) parameter or null if not availlable
  **/
  public final String getParameter(final String name, final int parameterType)
  {
    Hashtable parameters = (parameterType == QUERY_PARAM) ? queryParameters : bodyParameters;
    String[] vals = (String[]) parameters.get(name);
    if (vals == null)
    {
      return null;
    }
    return vals[0];
  }
  /**
   * Returns an array of String objects containing all of the values the given request
   * parameter has, or null if the parameter does not exist.
   * 
   * 
   * If the parameter has a single value, the array has a length of 1.
   * 
   * @param name a String containing the name of the parameter whose value is requested
   * @param parameterType type of parameter 
   * @see at.gv.egovernment.moa.id.util.ParametersInOrderServlet#QUERY_PARAM 
   * and @see at.gv.egovernment.moa.id.util.ParametersInOrderServlet#BODY_PARAM
   * @return an array of String objects containing the parameter's values or null
   * 
   * @see #getParameter
   */
  public final String getParameterValues(final String name, final int parameterType)
  {
    Hashtable parameters = (parameterType == QUERY_PARAM) ? queryParameters : bodyParameters;
    String[] vals = (String[]) parameters.get(name);
    if (vals == null)
    {
      return null;
    }
    String vallist = vals[0];
    for (int i = 1; i < vals.length; i++)
    {
      vallist = vallist + "," + vals[i];
    }
    return vallist;
  }
  /**
   * 
   * Returns an Enumeration of String objects containing the names of the
   * parameters. If there are no parameters, the method returns an empty
   * Enumeration.
   * 
   * @return an Enumeration of String objects, each String
   *         containing the name of a request parameter; or an empty Enumeration if the
   *         request has no parameters
   *  
   */
  public final Enumeration getParameterNames()
  {
  	Vector FullParamOrder = new Vector();
    for (Enumeration enu = queryParamOrder.elements(); enu.hasMoreElements();) {
       FullParamOrder.addElement(enu.nextElement());
    }  	
    for (Enumeration enu = bodyParamOrder.elements(); enu.hasMoreElements();) {
        FullParamOrder.addElement(enu.nextElement());
    }  	
    return FullParamOrder.elements();
  }
  
  /**
   * 
   * Returns an Enumeration of String objects containing the names of the
   * parameters contained in this request. If the request has no parameters, the method returns an empty
   * Enumeration.
   * @param parameterType type of parameter 
   * 
   * @return an Enumeration of String objects, each String
   *         containing the name of a request parameter; or an empty Enumeration if the
   *         request has no parameters
   *  
   */
  public final Enumeration getParameterNames(final int parameterType)
  {
    if (QUERY_PARAM == parameterType)
      return queryParamOrder.elements();
    else
      return bodyParamOrder.elements();
  }
}  //End InOrderServletRequestWrapper