//
// HTTPOutputSimple.java
//
// The contents of this file are subject to the SNIA Public License Version 1.0
// (the "License"); you may not use this file except in compliance with the
// License. You may obtain a copy of the License at
//
//    http://www.snia.org/resources/openSource.html
//
// 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 language governing rights and limitations
// under the License.
//
// The Original Code is HTTPOutputSimple.java
// 
// The Initial Developer of the Original Code is David Simons.
//
// Contributor(s):
//
//
// This source uses the Solaris WBEM APIs version 2.2. 
// Synchronization will be maintained with the Solaris WBEM APIS,
// which are being standardized through the Java Community Process.
//
//
// $Id: HTTPOutputSimple.java,v 1.1 2003/07/17 18:32:55 kumpf Exp $
//
// $Log: HTTPOutputSimple.java,v $
// Revision 1.1  2003/07/17 18:32:55  kumpf
// HP-BAPU: PEP-64 CIM Client and CIM Listener Java Interface
//
// Revision 1.1.1.1  2001/10/31 18:17:14  karl
// import of SNIA 0_7
//
// Revision 1.5  2001/06/23 11:31:37  davidsi
// - Support for SSL.
//
//
//
package org.snia.wbemcmd.xml;

import java.io.*;
import java.util.*;
import java.net.*;
import javax.net.ssl.*;
import org.snia.wbemcmd.utils.Log;
import org.snia.wbem.cim.CIMException;
import org.snia.wbemcfg.GlobalConfig;

/**
 *  Writes text out over HTTP using sockets protocol
 */
public class HTTPOutputSimple implements HTTPOutput 
{
    private int port;
    private String hostName;
    private String authToken = null;
    private String lastMethod;
    private String lastObjectPath;
    private Vector lastPrinted=new Vector();
    HTTPWriter out=null;
    Socket clientSocket=null;
    HTTPReader reader;

    private String protocol=new String("http");
    private boolean postRequestSent = false;
    private int timeout = -1;
    private boolean useHTTP10Only=false;


    //
    // Constructor
    //
    public HTTPOutputSimple() { }

   /**
    * setHostAndPort
    * 
    * @param ihostName
    *   The name of the host to connect to
    * @param iport
    *   The port to connect to
    */
    public void setHostAndPort( String ihostName,int iport ) 
    {
        port=iport;
        hostName=new String(ihostName);
    }

   /**
    * setHostAndPort
    * 
    * @param ihostName
    *   The name of the host to connect to
    * @param iport
    *   The port to connect to
    */
    public void setHostURL(String iProtocol, String iHostName,int iPort ) 
    {
        protocol  = iProtocol;
        port=iPort;
        hostName=new String(iHostName);
    }

    /**
     * Sets the timeout in milliseconds for the CIMClient.
     *
     * @param timeout - time in milliseconds.
     */
    public void setTimeout(int timeout)
    {
        this.timeout =  timeout;
    }


    /**
     * Gets the CIMClient timeout in milliseconds.
     *
     * @return time in milliseconds.
     */
    public int getTimeout()
    {
        return (this.timeout);
    }


   /**
    * Sets authentication details
    * 
    * @param userName
    *   The username
    * @param password
    *   Password for the user
    */
    public void authenticate( String userName, String password ) 
    {
        Base64Encoder b64e=new Base64Encoder(userName + ":" + password);
        authToken=b64e.processString();
    }


    /**
     * Closes the connection
     * @exception CIMException
     *   In case of unable to close the connection
     */
    public void close()  throws CIMException
    {
        HTTPWriter hw = getWriter();

        try
        {
            if (hw != null)
            {
                hw.close();
            }

            if (reader != null)
            {
                reader.close();
            }

            if( clientSocket != null)
            {
                // Close the SSLSession and the socket
                if(clientSocket instanceof SSLSocket)
                {
                    SSLSession session = ((SSLSocket)clientSocket).getSession();
                    session.invalidate();
                }
                clientSocket.close();
            }
            out = null;
            clientSocket = null;
            reader = null;
        }
        catch (IOException ioe) 
        {
            out = null;
            clientSocket = null;
            reader = null;
            Log.println("Failed to close the connection: " + ioe);
            throw new CIMException(CIMException.CIM_ERR_FAILED, ioe.getMessage());
        } 

    }


    /**
     * getWriter
     *   get socket connection outputstream for HTTP operation. 
     * @return HTTPWriter
     *   Socket output stream
     * @exception CIMException
     *   In case of unable to connect to the Server
     */
    private HTTPWriter getWriter() throws CIMException 
    {
        String rname="localhost";
        try 
        {
            if (out==null) 
            {
                SocketProvider sp = null;
                if ( protocol.equalsIgnoreCase("http") )
                {
                    // obtain the right socket provider from global config
                    if (GlobalConfig.httpSocketProvider != null)
                    {
                        try 
                        {
                             Class spClass = Class.forName(GlobalConfig.httpSocketProvider);
                             sp = (SocketProvider) spClass.newInstance();
                        }
                        catch (Exception exhttp ) 
                        {
                             Log.println("HTTPOutputSimple: Error loading httpSocketProvider \"" +
                                 GlobalConfig.httpSocketProvider + "\", message : " +
                                 exhttp.getLocalizedMessage());
                        }
                    }
                }
                else
                {
                    // obtain the right socket provider from global config
                    if (GlobalConfig.httpsSocketProvider != null)
                    {
                        try 
                        {
                            Class spClass = Class.forName(GlobalConfig.httpsSocketProvider);
                            sp = (SocketProvider) spClass.newInstance();
                        }
                        catch (Exception exhttps ) 
                        {
                            Log.println("HTTPOutputSimple: Error loading httpsSocketProvider \"" +
                                GlobalConfig.httpsSocketProvider + "\", message : " +
                                exhttps.getLocalizedMessage());
                        }
                    }
                }

                // if none specified or if it could not be loaded use default
                if (sp == null)
                {
                    Log.println("HTTPOutputSimple: No SocketProvider was specified - " +
                        "plainSocketProvider is used.");
                    sp = new PlainSocketProvider();
                }

                if (hostName==null)
                {
                    clientSocket=sp.createSocket(InetAddress.getLocalHost(),port);
                }
                else 
                {
                    rname=hostName;
                    clientSocket=sp.createSocket(InetAddress.getByName(hostName),port);
                }

                if (timeout != -1)
                {
                    clientSocket.setSoTimeout(timeout);

                    if ( GlobalConfig.DEBUG_XML )
                    {
                        Log.println("Socket timeout set to : "+ clientSocket.getSoTimeout());
                    }
                }

                reader = new HTTPReader(clientSocket);
                out = new HTTPWriter(clientSocket.getOutputStream(),
                    HTTPWriter.CIM_HTTP_OP_METHODCALL,rname);
            } 

            return(out);
        } 
        catch ( UnknownHostException uhe) 
        {
            if ( GlobalConfig.DEBUG_XML )
                Log.println("Socket creation exception: " + uhe.getMessage() );

            throw new CIMException(
                CIMException.CIMCLIENT_ERR_CONNECTION_FAILED, uhe.getMessage());
        } 
        catch ( CannotCreateSocketException ccse) 
        {
            if ( GlobalConfig.DEBUG_XML )
                Log.println("Socket creation exception: " + ccse.getMessage() );

            throw new CIMException(ccse.getID(), ccse.getMessage());
        } 
        catch ( java.net.ConnectException ce ) 
        {
            if ( GlobalConfig.DEBUG_XML )
                Log.println("Connection exception: " + ce.getMessage() );

            throw new CIMException(
                CIMException.CIMCLIENT_ERR_CONNECTION_FAILED, ce.getMessage());
        } 
        catch ( IOException ioe ) 
        {
            if ( GlobalConfig.DEBUG_XML )
                Log.println("IO exception: "+ ioe.getMessage());

            throw new CIMException(
                CIMException.CIMCLIENT_ERR_CONNECTION_FAILED, ioe.getMessage());
        }
        catch ( Exception e ) 
        {
            if ( GlobalConfig.DEBUG_XML )
                Log.println("Exception: "+ e.getMessage());

            throw new CIMException(
                CIMException.CIMCLIENT_ERR_CONNECTION_FAILED, e.getMessage());
        }
    }

    
   /**
    * getReader()
    *   get socket connection inputstream for HTTP response. 
    * @return HTTPReader
    *   Socket input stream
    * @exception CIMException
    *   In case of unable to connect to the Server
    */
    public HTTPReader getReader()throws CIMException 
    {
        getWriter();
        return(reader);
    }
    
   /**
    * setMethod
    *   sets the CIM Method Name
    * @param iMethodName
    *   Name of CIM Method
    */
    public void setMethod(String iMethodName) throws CIMException
    {
        lastMethod=iMethodName;
        HTTPWriter hw=getWriter();
        hw.setMethodName(iMethodName);
    }


    
   /**
    * setObjectPath
    *   sets the CIM Object Path
    * @param iMethodName
    *   Name of CIM Object Path
    * @exception CIMException
    *   In case of unable 
    */
    public void setObjectPath(String iObjPath) throws CIMException
    {
        lastObjectPath=iObjPath;
        HTTPWriter hw=getWriter();
        hw.setObjectPath(iObjPath);
    }



   /**
    * sendRequest
    *   Writes HTTP request to the Server
    * @param XMLPayload
    *   XML request payload consisting a CIM operation.
    * @return XMLNode
    *   XML node tree consisting XML-CIM response.
    */
    public XMLNode sendRequest(String XMLPayload) throws CIMException 
    {
        return(sendRequest(XMLPayload, false, false));
    }
        



   /**
    * sendRequest
    *   Writes HTTP request to the Server
    * @param XMLPayload
    *   XML request payload consisting a CIM operation.
    * @param isFeatures
    *   TODO -isFeatures, not implemented. So set to false for now.
    * @return XMLNode
    *   XML node tree consisting XML-CIM response.
    */
    private XMLNode sendRequest(String XMLPayload, boolean isFeatures, 
                    boolean isHTTP10) throws CIMException 
    {
        HTTPWriter hw=getWriter();

        if (isFeatures) 
        {
           // TODO: OPTIONS request support.
        }
    
        if (authToken != null)
        {
            hw.setAuth(authToken);
        }

        if (XMLPayload!=null)
        {
            hw.println(XMLPayload);
        }
        
        hw.println("</MESSAGE></CIM>");
        try 
        {
            hw.flush(isHTTP10);
        } 
        catch (HTTPException httpe) 
        {
            if ( httpe.getErrorCode() == HTTPWriter.SC_REQUEST_TIMEOUT )
                throw new CIMException(CIMException.CIMCLIENT_ERR_TIMED_OUT, 
                    httpe.getMessage());
            else
                throw new CIMException(CIMException.CIMCLIENT_ERR_HTTP_ERROR, 
                    httpe.toString());
        }
    

        if (GlobalConfig.forceYield)
            Thread.yield();

        try 
        {
            XMLNode ret=reader.parseRequest(null,true);
            postRequestSent = false;
            lastPrinted.removeAllElements();
            if (reader.isForceClose()) 
            {
                if (out!=null) 
                {
                    out.close();
                }
                out=null;
                reader=null;
            }
            return(ret);
        } 
        catch (java.net.SocketTimeoutException ste) 
        {
            throw new CIMException(CIMException.CIMCLIENT_ERR_TIMED_OUT, 
                ste.getMessage());
        }
        catch (IOException ioe) 
        {
            Log.println("Failed to read the response, exception: " + ioe);
            out=null;
            throw new CIMException(CIMException.CIM_ERR_FAILED, ioe.getMessage());
        } 
        catch (HTTPException httpe) 
        {
            if ( httpe.getErrorCode() == HTTPWriter.SC_REQUEST_TIMEOUT )
            {
                throw new CIMException(CIMException.CIMCLIENT_ERR_TIMED_OUT, 
                    httpe.getMessage());
            }

            if (httpe.getErrorCode()==HTTPWriter.SC_UNAUTHORIZED) 
            {
                Log.println("User Authorization failed." + httpe.getMessage());
                throw new CIMException(CIMException.CIM_ERR_ACCESS_DENIED,
                    httpe.getMessage());
            }
    
            //
            // Got "501/510 Not implemented", try POST request.
            //
            if (httpe.getErrorCode()==HTTPWriter.SC_NOT_IMPLEMENTED) 
            {
    
                Log.println("Sending a POST request.");

                if (!postRequestSent)
                {
                    //
                    // If we come here then CIMOM only understands HTTP/1.0 requests
                    //
                    useHTTP10Only = true;
                    postRequestSent = true;
    
                    // Setup retry with authentication token
                    hw=getWriter();
                    hw.setMethodName(lastMethod);
                    hw.setObjectPath(lastObjectPath);
                    for(int i=0;i<lastPrinted.size();i++)
                        hw.println((String)lastPrinted.elementAt(i));

                    return sendRequest(XMLPayload, isFeatures, useHTTP10Only);
                } 
                else 
                {
                    Log.println("POST Method not implemented on the server: " +
                        httpe.getMessage());
                    throw new CIMException(CIMException.CIMCLIENT_ERR_HTTP_ERROR, 
                        httpe.getMessage());
                }
            }

            //
            // Handle BAD request
            //
            if (httpe.getErrorCode()==HTTPWriter.SC_BAD_REQUEST) 
            {
                Log.println("Bad HTTP request: " + httpe.getMessage());
                throw new CIMException(CIMException.CIMCLIENT_ERR_HTTP_ERROR, 
                    httpe.getMessage());
            }

            // Unknown error
            Log.println("Unknown error: " + httpe.getMessage());
            out=null;
            throw new CIMException(CIMException.CIMCLIENT_ERR_HTTP_ERROR, 
                httpe.getMessage());
        }
    }


   /**
    * println
    *   Writes the line to the HTTP output stream
    * @param string
    *   Data to write on to output stream
    */
    public void println(String string) throws CIMException
    {
        lastPrinted.add(string);
        HTTPWriter hw=getWriter();
        hw.println(string);
    }

   /**
    * println
    *   Adds the CIM-XML message ID
    * @param id
    *   CIM-XML message ID
    */
   public void makeHeader(String id) throws CIMException
   {
       println(HTTPWriter.XML_CIM_HEADER1);
       println(HTTPWriter.XML_CIM_HEADER2);
       println("<MESSAGE ID=\""+id+"\" PROTOCOLVERSION=\"1.0\">");
   }

   /**
    * getFeatures
    *   Not supported now. Will be use when CIMOMs support
    *   HTTP OPTIONS methods
    */
   public String getFeatures() throws CIMException 
   {
       sendRequest(null, true, useHTTP10Only);
       return("");        
   }

}

