/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2003 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 Jetspeed" 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" or
 *    "Apache Jetspeed", 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 org.apache.jetspeed.services.webpage;

// java.io
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;

// java.util
import java.util.Iterator;
import java.util.Enumeration;
import java.util.HashMap;

// javax.servlet
import javax.servlet.http.*;

// java.net
import java.net.URL;
import java.net.URLConnection;
import java.net.HttpURLConnection;
import java.net.URLEncoder;

import org.apache.log4j.Logger;

/*
 * Abstract Base class for web page sessions.
 * Implements the primary dispatcher logic for getting and posting resources.
 *
 */

public abstract class AbstractSiteSession implements SiteSession
{    

    // the base url of the host being proxied
    protected String targetBase;

    // the base url the wps
    protected String proxyBase;

    // the cookies collection
    protected HashMap cookies = new HashMap();
        
    // counters
    protected int hitCount = 0;
    protected int cacheCount = 0;

    // the log file singleton instance
    static Logger log = Logger.getLogger(AbstractSiteSession.class);

    /**
     * Create a NetElementSession, which maintains sessions with one network element.
     * 
    * @param targetBase the target host's base URL
    * @param proxyBase the proxy server host URL base address.     
     */    
    public AbstractSiteSession(String targetBase, String proxyBase)
    {
        this.proxyBase  = proxyBase;
        this.targetBase = targetBase;
    }

    /**
     * Given a URL, returns the content from that URL in a string.  
     * All HTTP hyperlinks(HREFs) are rewritten as proxied-referenced hyperlinks.
     * All relative references to web resources (images, stylesheets, ...) are 
     * rewritten as absolute references, but are not proxied.
     * Determines if we are logged on to the target site. If not,
     * calls logon(), which is a implementation specific 'POST' exchange
     *
     * @see logon(String, HttpServletRequest, HttpServletResponse)     
     *
     * @param url the proxied resource address.
     * @param data the rundata
     *
     * @exception IOException a servlet exception.
     */    

    public void dispatch(String url, ProxyRunData data)
                 throws IOException
    {                    
        try 
        {
            Configuration config = Configuration.getInstance();
                
            log.debug("=== Dispatching =" + url);
            
            // open a pooled HTTP connection
            //URL u = new URL(url);
            URL u = new URL(null, url, new sun.net.www.protocol.http.Handler() );
            HttpURLConnection con = (HttpURLConnection)u.openConnection();  
    
            //if (con instanceof HttpURLConnection) 
            con.setDoInput(true);
            con.setDoOutput(true);
            con.setAllowUserInteraction(false);        
            con.setFollowRedirects(false);
    
            if (data.getPosting()) {
                con.setRequestMethod("POST");
            }
        
            // are there any cookies in our current session?
            if (cookies.isEmpty()) 
            {
                // there are no cookies, must be a new session, so lets logon
                    log.debug("... no session id provided. Logging on...");
    
                if (false == logon(data))
                    return;   
    
            }        
    
            // send the cookies (session ids) back to the NE
            Iterator it = cookies.values().iterator();
            Cookie cookie;
            while (it.hasNext()) {
                cookie = (Cookie)it.next();
                String sessionID = WebPageHelper.buildCookieString(cookie);
                con.setRequestProperty("Cookie", sessionID);
                log.debug("... Sending Session ID: " + sessionID );
            }            
    
            // we have to get the post parameters from the servlet container,
            // and then re-encode them. i wish i could find out how to keep
            // the servlet container from reading them, because now i have to
            // parse them out, and then re-encode
            if (data.getPosting()) {
    
                // get the post params 
                StringBuffer postParams = new StringBuffer();
                int count = 0;
                Enumeration e = data.getRequest().getParameterNames();
                while (e.hasMoreElements())
                {
    
                    String name = (String) e.nextElement();
                    if (name.equals(config.getSID()) ||
                        name.equals(config.getURL())) {
                        continue;
                    }                                      
    
                    String values[] = data.getRequest().getParameterValues(name);
                    if (values != null) 
                    {
                        for (int i = 0; i < values.length; i++) {
                            if (count > 0) {
                                postParams.append("&");
                            }
                            postParams.append(name);
                            postParams.append("=");
                            postParams.append(URLEncoder.encode(values[i]));
                            count++;
                        }
                    }
                }
                String  postString = postParams.toString();            
                con.setRequestProperty("Content-length", String.valueOf(postString.length()) );
                // write directly to the output stream            
                DataOutputStream dos = new DataOutputStream(con.getOutputStream());            
    
                log.debug("... POST: " + postString);
                dos.writeBytes(postString);
                dos.close();
            }
           
            int rc = con.getResponseCode();
    
            // Get the Session Information from Headers
            int contentType = WebPageHelper.getContentType(con.getHeaderField("content-type"), u.toString());
            String location = con.getHeaderField("Location");
            
            if ((rc == con.HTTP_MOVED_PERM || rc == con.HTTP_MOVED_TEMP) && null != location) 
            {
                log.debug("+++ REDIRECT = " + location);
                location = WebPageHelper.concatURLs(targetBase, location);
                dispatch(location , data);
                return;
            }
    
            // get cookies set from server
            String cookieString = con.getHeaderField("Set-Cookie");
            if (null != cookieString) 
            {
                log.debug("... new SessionID found: " + cookieString);
                WebPageHelper.parseCookies(cookieString, this);            
            }
    
            if (contentType == WebPageHelper.CT_IMAGE || 
                contentType == WebPageHelper.CT_BINARY ||
                contentType == WebPageHelper.CT_APPLICATION) {
                // wasn't in the cache, get it from host
                getBinaryContent(con, data.getResponse());
                return;
            }
        
            rewriteContent(data, con, contentType, url);
    
        }
        catch (IOException ex)
        {
            log.error("*** PROXY DISPATCH EXCEPTION = " + ex);
                throw ex;
        }

    }

    /**
     * Gets the HTML content from the URL Connection stream and returns it
     * in a string
     *
     * @param con The URLConnection to read from.
     * @param resource The full URL of the resource.
     * @return The HTML Content from the stream.
     *
     * @exception IOException a servlet exception.
     */
    public String getHTMLContent(URLConnection con, 
                                 ProxyRunData data,
                                 String resource) throws IOException 
    {

        int CAPACITY = 4096;

        InputStream is = con.getInputStream();
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        
        Configuration config = Configuration.getInstance();
        FileOutputStream fos = null;
        boolean logging = config.getEnableContentLog();
        if (logging) 
        {
            if (data != null)
            {
                String fileName = data.getServlet().getServletContext().getRealPath(
                                        config.getLogLocation() );
                fos = new FileOutputStream(fileName, true);
                WebPageHelper.writeHeader(fos, resource);
            }
        }
        
        //now process the InputStream...
        
        byte[] bytes = new byte[CAPACITY];

        int readCount = 0;
        int total = 0;

        while( ( readCount = is.read( bytes )) > 0 ) 
        {                                        
            buffer.write( bytes, 0, readCount);
            if (logging)
            {
                fos.write( bytes, 0, readCount);
            }
            total += readCount;
        }     
        if (logging) 
        {
            fos.close();
        }
        is.close();

        return buffer.toString();
    }

    /**
     * Gets the HTML content from the URL Connection stream and writes it to respones
     *
     * @param con The URLConnection to read from.
     *
     * @exception IOException a servlet exception.
     */
    public void getBinaryContent(URLConnection con,
                                 HttpServletResponse response) throws IOException 
    {

        int CAPACITY = 4096;


        InputStream is = con.getInputStream();

       // FileOutputStream fos = new FileOutputStream("/test.fw", true);

        //now process the InputStream...
        
        byte[] bytes = new byte[CAPACITY];

        int readCount = 0;
        while( ( readCount = is.read( bytes )) > 0 ) {
                                        
            response.getOutputStream().write(bytes, 0, readCount);
            //fos.write( bytes, 0, readCount);
        }        
        
        //fos.close();
        is.close();

    }

    /**
      * Given a cookie, it first checks to see if that cookie is already
      * managed in this session. If it is, it means that the session has
      * timed out and that the network element has now created a new session.
      * In that case, replace the cookie, and re-establish the session (logon)
      * If its a new cookie, we will still need to logon, and and the cookie to
      * the managed cookies collection for this session.
      *
      * @param cookie new cookie returned from target server.
      * @return true when a new cookie added, false when updated.
      *
      */
    public boolean addCookieToSession(Cookie cookie)
    {
        boolean added = (null == cookies.get(cookie.getName()));
        cookies.put(cookie.getName(), cookie); // adds or updates        
        return added;
    }

    /*
     * Gets the hitcount for this session.
     *
     * @return the hitcount for this session.
     */
    public int getHitCount()
    {
        return hitCount;
    }

    /*
     * Increments the hitcount for this session.
     *
     */
    public void incHitCount()
    {
        hitCount++;
    }

    /*
     * Gets the cache count for this session.
     *
     * @return the cache count for this session.
     */
    public int getCacheCount()
    {
        return cacheCount;
    }

    /*
     * Increments the hitcount for this session.
     *
     */
    public void incCacheCount()
    {
        cacheCount++;
    }


}

