/*  Copyright (c) 2000, 2002 by Oracle Corporation. All Rights Reserved. */

package oracle.portal.provider.v1.http; 

import java.util.Vector;
import java.util.Enumeration; 
import java.util.Properties; 
import java.io.PrintWriter; 
import java.io.InputStream; 
import java.io.IOException; 
import java.io.Reader;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.UnsupportedEncodingException;
import java.net.URL; 
import java.net.MalformedURLException; 
import oracle.portal.provider.v1.ProviderUser; 
import oracle.portal.provider.v1.Provider; 
import oracle.portal.provider.v1.ExternalPrincipal; 
import oracle.portal.provider.v1.ProviderException; 
import oracle.portal.provider.v1.ProviderSession; 
import oracle.portal.provider.v1.AuthenticationException; 
import oracle.portal.provider.v1.ExternalAuthenticationException; 
import oracle.portal.provider.v1.PortletException; 
import oracle.portal.provider.v1.ProviderSubscriber; 
import oracle.portal.provider.v1.ProviderException; 
import oracle.portal.provider.v1.ProviderLog; 
import oracle.webdb.utils.SimpleStringBuffer;
import oracle.portal.utils.v1.MultiByteEncoder;
import oracle.portal.utils.v1.ParameterMap;
import javax.servlet.ServletConfig; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpSession; 
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionBindingEvent;
import HTTPClient.ModuleException;
import HTTPClient.HTTPConnection;
import HTTPClient.HTTPResponse;
import HTTPClient.CookieModule; 
import HTTPClient.Cookie; 
import HTTPClient.NVPair;
import HTTPClient.AuthorizationInfo;

import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileOutputStream; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; 
import java.io.OutputStreamWriter; 
import java.util.Hashtable; 
import java.util.StringTokenizer; 


/**
 * This class is responsible for overriding the initSession method needed 
 * for cookie management. Cookies retrieved from external application will 
 * be stored in servlet session which can be utilised in later stages. 
 * It checks for login authentication and throws the login screen once the 
 * login credentials are false.
 *
 * <p> PDK-URL Services supports two types of authentication.
 * Care has been taken for two types of authentication.
 * <ul>
 * <li><b>Form based Authentication : </b><br>
 * User information is fetched from external application of portal,
 * then submitted to the server side authenticating application. Cookies
 * are extracted and stored in servlet session to be used in later stages 
 * while rendering.
 * </li>
 *
 * <li><b>Basic Authentication :</b><br>
 * External Applicaton {@link oracle.portal.provider.v1.ExternalPrincipal} 
 * details are used to set AuthorizationInformation needed by BASIC 
 * authentication portlets using HTTPClient. 
 * </li></ul>
 *
 */
public class URLProvider extends DefaultProvider 
{ 
    
    //Class for holding authentication details.
    private Authentication mAuthentication = null; 
    
    //Class for holding http proxy information
    private ProxyInformation mHttpProxyInformation = null; 
    
    //Class for holding https proxy information
    private ProxyInformation mHttpsProxyInformation = null; 
    
    //Class for holding Subscriber ID information
    private SubscriberDetails mSubscriberDetails = null; 
    
    // Pointer to Provider.xml file
    private String mMapFilePath = null; 
    private String mCompleteMapFilePath = null; 
    private File mMapFile = null; 
    private String mSubscriberMapFilePath = null; 
    private File mSubscriberFile = null; 
    
    // init arg parameter
    private static final String PROVIDER_TO_KEY_MAP_ROOT = "provider_root"; 
    private static final String PROV_ID_TO_PROV_KEY_FILE_NAME = 
        "providerKeyToSubscriberInfoMap.ser"; 
    // memory map hashtable for Subscriber keys
    private Hashtable mProviderIDToProviderKeyMap = new Hashtable(); 
    private final Object mUrlLock = new Object();

    static
    {
        AuthorizationInfo.setAuthHandler(new URLAuthHandler());    
    }

   
    /**
     * Override superclass's init method to get the debuglevel from 
     * zone.properties using ServletConfig object.
     *
     */
    protected void init(long providerId, ProviderLog logger, 
        ServletConfig sc, 
        HttpServletRequest request) 
    throws ProviderException 
    { 
        super.init(providerId,logger,sc,request); 
        String handler = System.getProperty("java.protocol.handler.pkgs");
        if(handler == null || !handler.equals("HTTPClient"))
        {
            System.setProperty("java.protocol.handler.pkgs", "HTTPClient");
        }
        
        UrlServicesUtils.logMessage(
            this,
            "Entering init *********************", 
            getDebugLevel(),
            UrlServicesUtils.BASIC_LOGGING); 
            
        UrlServicesUtils.logMessage(
            this,
            "Debug Level is : " + getDebugLevel(), 
            getDebugLevel(),
            UrlServicesUtils.BASIC_LOGGING); 

        //Create an array of cookies that shall be returned by initSession
        //Sets Url Cookie policy hadler which takes care of cookie 
        //extraction logic.
        setUrlCookiePolicyHandler(); 
        
        // we can use getProviderRepositoryPath() method alternatively
        mMapFilePath = sc.getInitParameter(PROVIDER_TO_KEY_MAP_ROOT); 
        if (mMapFilePath == null) 
        { 
            // See whether the provider root is specified.
            // We can look for the map file there.
            mMapFilePath = getProviderRepositoryRealPath(sc); 
        } 
        UrlServicesUtils.logMessage(
            this,
            "Leaving init *****************", 
            getDebugLevel(),
            UrlServicesUtils.BASIC_LOGGING); 
            
    } //end of init
    
    /**
     *  Called to initialize the Provider in a particular user session.  This
     *  may be called never, once per user session, or once per request depending
     *  on how the provider is configured in the portal.  If the Provider uses
     *  HTTP cookies or maintains state via a cookie-based HTTP session, it is
     *  imperative that (all) cookies be established during this call.
     *
     *  initSession does following
     *   -Calls super to call initSession method of DefaultProvider
     *   -Extracts login url and login information from portal login server
     *   -Call private helper method extractCookies passing login information
     *   -Sets cookies names , values and other attributes
     *   -Returns array of cookie objects back. This is returned to portal
     *   -Also sets cookie string in session storage for retrival during render
     *    time.
     *
     *  @param user  Current provider user. Used for getting provider session 
     *         and the portal user name.
     *  @throws ProviderException
     *  @return Array of cookie objects back. This is returned to portal
     */
    public Object[] initSession(ProviderUser user, 
        ExternalPrincipal externalApp) 
    throws ProviderException 
    { 
        Object cookies[] = super.initSession(user,externalApp); 
        UrlServicesUtils.logMessage(this,"Entering initSession ***************",
            getDebugLevel(),UrlServicesUtils.BASIC_LOGGING); 
        UrlServicesUtils.logMessage(this,"initSession : " +
            "ExternalPrincipal Object = " + externalApp,getDebugLevel()); 
        final ProviderSession session = user.getSession(false); 
        if(session == null)
        {
            throw new ProviderException("Your session is null. Please login to portal again.");
        }

        // Make sure HTTP Client forgets all the cookies for this session when
        // the session is invalidated
        // Storing an object implementing HttpSessionBindingListener
        // in HttpSession makes sure its corresponding valueBound and 
        // valueUnbound methods are invoked whenever session is created 
        // and destroyed respectfully.
        session.setAttribute("clear.context", new HttpSessionBindingListener()
        {
            public void valueBound(HttpSessionBindingEvent event)
            {
                UrlServicesUtils.logMessage(URLProvider.this, "extractCookies : " +
                    "valueBound method invoked.", getDebugLevel());
            }

            public void valueUnbound(HttpSessionBindingEvent event)
            { 
                UrlServicesUtils.logMessage(URLProvider.this, "extractCookies : " +
                    "Clearing cookies from CookieModule for the " + 
                    "current provider context.", getDebugLevel());
                CookieModule.discardAllCookies(session.getId());    
                URLAuthHandler.removeAuthorization(session.getId());
            }
        });

        Authentication authentication = null; 
       
        if (getAuthentication() != null) 
        { 
            authentication = (Authentication)getAuthentication(); 
        } 
        else  
        { 
            StringBuffer sbuf = new StringBuffer();
            sbuf.append("Authenticaion tag cannot be null. " + 
                        " Please provide authentication details" +
                        " in provider.xml or make the provider login " +
                        " frequency as 'Never'"); 
            AuthenticationException e = 
                new AuthenticationException(sbuf.toString());
            UrlServicesUtils.logMessage(this,
                sbuf.toString(),
                e,getDebugLevel()); 
            throw e; 
        } 
        String authType = authentication.getAuthType(); 
        

        // Check for authType XML tag value and act accordningly
        // Otherwise raise an exception in portlet
        if (authType.equalsIgnoreCase(UrlServicesUtils.EXTERNAL)) 
        { 
            // A vector to hold information about cookies retrived
            Vector myCookieDataVector = new Vector(); 
            
            UrlServicesUtils.logMessage(this,
                "initSession : called setUrlCookiePolicyHandler " ,getDebugLevel()); 
            ServletProviderSession sSession = (ServletProviderSession)session;
            UrlServicesUtils.logMessage(this,
                "initSession : Session Id is:" + session.getId(),
                getDebugLevel()); 
            UrlServicesUtils.logMessage(this,
                "initSession : Provider Session Id is:" + 
                user.getPortalSessionId(),
                getDebugLevel()); 
            UrlServicesUtils.logMessage(this,
                "initSession : Portal User Name is:" + user.getName(),
                getDebugLevel()); 
            UrlServicesUtils.logMessage(this,
                "initSession : Mapped External User Name is:" + 
                user.getMappedName(),
                getDebugLevel()); 
            //Hard coding a safe character set which can be applied to most 
            //of the strings.
            String encoding = "UTF-8";
            String params = makeQueryString(externalApp, encoding);
            UrlServicesUtils.logMessage(this, 
                "initSession : Encoded query string :" + params, 
                getDebugLevel());
            
            boolean loginSuccess = extractCookies(sSession,
                externalApp.getAuthUrl(), 
                params, 
                externalApp.getAuthMethod());
            if (loginSuccess == true) 
            { 
                // Also set in session servlet storage to indicate 
                // registered user
                session.setAttribute(UrlServicesUtils.REGISTERED_FLAG,
                    Boolean.TRUE); 
            } // End of check for cookies not being present
            else
            { 
                UrlServicesUtils.logMessage(this,
                    "initSession: Registration failed. Setting Registered " +
                    "flag in servlet session to false.",getDebugLevel()); 
                    
                // If no cookies are found then indicate in session storage 
                // that the person is a non registered user
                session.setAttribute(UrlServicesUtils.REGISTERED_FLAG,
                    Boolean.FALSE); 
                throw new AuthenticationException("Error : User is not " + 
                    "authenticated to view the portlet. Please use the " + 
                    "link give below to update your login information.");
            } 
            UrlServicesUtils.logMessage(this,
                "URLProvider:initSession : before returning",getDebugLevel()); 
        } // End of check for authtype being ExternalApp kind
        else if (authType.equalsIgnoreCase(UrlServicesUtils.BASIC_AUTHENTICATION)) 
        { 
            //Fix up authorization information in auth handler
            URLAuthHandler.addAuthorization(
                session.getId(),
                new NVPair(externalApp.getName(),
                           externalApp.getPassword()));
                           
            return cookies;
        } 
        else  
        { 
            AuthenticationException ae = new AuthenticationException( 
                "Invalid authentication type specified. "+ 
                "Please select between 'externalApp' or 'basic'"); 
            UrlServicesUtils.logMessage(this, 
                "Invalid authentication type specified.", 
                ae,getDebugLevel()); 
            throw ae; 
        } 
        return cookies;
      
    } 
    
    /**
     * <p>
     * setUrlCookiePolicyHandler method is a public method which 
     * set CookieModule.setCookiePolicyHandler(null), which means we are 
     * using default implemenatation. Hence diffent sites can have there
     * own cookie handler which override setCookiePolicyHandler.</p>
     * <B> CookiePolicyHandler </B> <BR>
     *
     * <P>Sets a new cookie policy handler. This handler will be called for 
     * each cookie that a server wishes to set and for each cookie that this 
     * module wishes to send with a request. In either case the handler may 
     * allow or reject the operation. If you wish to blindly accept and send 
     * all cookies then just disable the handler with 
     * CookieModule.setCookiePolicyHandler(null);.</P>
     *
     * <P>At initialization time a default handler is installed. This handler 
     * allows all cookies to be sent. For any cookie that a server wishes to 
     * be set two lists are consulted. If the server matches any host or 
     * domain in the reject list then the cookie is rejected; if the server 
     * matches any host or domain in the accept list then the cookie is 
     * accepted (in that order). If no host or domain match is found
     * in either of these two lists and user interaction is allowed then 
     * a dialog box is poped up to ask the user whether to accept or reject 
     * the cookie; if user interaction is not allowed the cookie is
     * accepted.</P>
     *
     * <P>The accept and reject lists in the default handler are initialized 
     * at startup from the two properties HTTPClient.cookies.hosts.accept 
     * and HTTPClient.cookies.hosts.reject. These properties must contain 
     * a "|" separated list of host and domain names. All names beginning 
     * with a "." are treated as domain names, all others as host names. An 
     * empty string which will match all hosts. The two lists are further 
     * expanded if the user chooses one of the "Accept All from Domain" or
     * "Reject All from Domain" buttons in the dialog box.</P>
     */
    public void setUrlCookiePolicyHandler() 
    { 
        CookieModule.setCookiePolicyHandler(null);
    } 
     
    /**
     * overriding getSubscriber method of DefaultProvider 
     *    {@link oracle.portal.provider.v1.http.DefaultProvider}
     * @return ProviderSubscriber
     *
     */
    public ProviderSubscriber getSubscriber() 
    { 
        super.getSubscriber(); 
        String subscriberID = null;
        synchronized(mUrlLock)
        {
            if (mProviderIDToProviderKeyMap.size() == 0) 
            { 
                UrlServicesUtils.logMessage(this,
                    "ProviderSubscribe : Hashtable doesn't have any values. " + 
                    "Loading from file ...", getDebugLevel()); 
                createSerializedFile(); 
                loadSubscriberInfo(mMapFile); 
            } 
            // Get the provider key etc. from provider.xml
            subscriberID = 
                (String)mProviderIDToProviderKeyMap.get(new Long(getProviderId())); 
        }
        UrlServicesUtils.logMessage(this,
            "ProviderSubscriber:getSubscriber -- ProviderId & subscriberID -- " + 
            getProviderId() + " & " + subscriberID ,getDebugLevel()); 
        if (subscriberID == null) 
        { 
            return new ProviderSubscriber(null,null); 
        } 
        
        // We don't have a handle to encryption key so we pass null.
        return new ProviderSubscriber(subscriberID, null); 
    } // getSubscriber()
    
    /**
     * overriding register method of DefaultProvider 
     *    {@link oracle.portal.provider.v1.http.DefaultProvider}
     * @parm String subscriberID
     * @parm String portalVersion
     * @return ProviderSubscriber
     *
     */
    public ProviderSubscriber register(String subscriberID, 
        String portalVersion) 
    throws ProviderException 
    { 
        super.register(subscriberID, portalVersion); 
        UrlServicesUtils.logMessage(this,
            "register : Entering register() method ...",
            getDebugLevel(),
            UrlServicesUtils.BASIC_LOGGING); 
        if (mSubscriberDetails == null) 
        { 
            UrlServicesUtils.logMessage(this,
                "register : No subscriberDetails tag found. Continuing ...",
                getDebugLevel()); 
            return new ProviderSubscriber(subscriberID,null); 
        } 
        String subscriberKeys = null; 
        subscriberKeys = mSubscriberDetails.getSubscriberKeys(); 
        if (subscriberKeys == null) 
        { 
            UrlServicesUtils.logMessage(this,
                "register : Subscriber Keys were not found in provider.xml. " +
                "Continuing ...",getDebugLevel()); 
            
            //Just return from here, as we dont have to do any processing ...
            return new ProviderSubscriber(subscriberID, null); 
        } 
        
        // calling a method parse the mSubscriberKeys
        Vector sKeys = this.parseSubscriberID(subscriberKeys); 
        if (sKeys.contains(subscriberID)) 
        { 
            UrlServicesUtils.logMessage(this,
                "register : subscriberID/mSubscriberKeys exists.",
                getDebugLevel()); 
            synchronized(mUrlLock)
            {
                //check if persistent storage of subscriber is requied
                if (mSubscriberDetails.
                    getSubscriberInfo().
                    equalsIgnoreCase(UrlServicesUtils.TRUE)) 
                { 
                    mProviderIDToProviderKeyMap.put(
                        new Long(
                            getProviderId()), 
                            subscriberID); 
                    UrlServicesUtils.logMessage(this,
                        "ProviderSubscriber:register mProviderIDToProviderKeyMap " +
                        "contains " + mProviderIDToProviderKeyMap,
                        getDebugLevel()); 
                    createSerializedFile(); 
                    // prepare to store subscriberKeys persistent
                    // Make the map persistent here.
                    this.storeSubscriberInfo(mMapFile); 
                    this.storeDetails(
                        Long.toString(getProviderId()), 
                        "provider is registered", true); 
                }
                else  
                { 
                    UrlServicesUtils.logMessage(this,
                        "register : ### Portal Error ### --",
                        getDebugLevel()); 
                } // condition checking for subscriber key's vector
            } 
        } 
        // if mSubscriberInfo is also true, 
        // then we make persistent storage of the subscriber keys
        UrlServicesUtils.logMessage(this,
            "register : Leaving register() method ...",
            getDebugLevel(),
            UrlServicesUtils.BASIC_LOGGING); 
        
        // We don't have a handle to encryption key as yet so we pass null.
        return new ProviderSubscriber(subscriberID, null); 
    } // ProviderSubscriber() method
    
    /**
     * overriding deregister method of DefaultProvider 
     * {@link oracle.portal.provider.v1.http.DefaultProvider}
     *
     */
    public void deregister() throws ProviderException 
    { 
        super.deregister(); 
        synchronized(mUrlLock)
        {
            // Delete this Provider from the Map.
            // Update the persistent map here.
            this.storeSubscriberInfo(mMapFile); 
            this.storeDetails(
                Long.toString(getProviderId()), 
                "provider is removed",
                false); 
            mProviderIDToProviderKeyMap.remove(new Long(getProviderId()));
        }
    } // deregister method
    
    // private methods for Subscriber id's
    
    /**
     * Helper method to load SubscriberKey from persistent file.
     */
    private void loadSubscriberInfo(File pMapFile) 
    { 
        FileInputStream fis = null; 
        ObjectInputStream ois = null; 
        UrlServicesUtils.logMessage(this,"URLProvider:loadSubscriberInfo: Entered ...", getDebugLevel()); 
        try  
        { 
            fis = new FileInputStream(pMapFile); 
            UrlServicesUtils.logMessage(this,"URLProvider:loadSubscriberInfo: Created File Inputstream ..." + fis, getDebugLevel()); 
            ois = new ObjectInputStream(fis); 
            UrlServicesUtils.logMessage(this,"URLProvider:loadSubscriberInfo: Created Object Inputstream ..." + ois, getDebugLevel()); 
            mProviderIDToProviderKeyMap = (Hashtable) ois.readObject(); 
            UrlServicesUtils.logMessage(this,"URLProvider:loadSubscriberInfo: Object read from file is " + mProviderIDToProviderKeyMap, getDebugLevel()); 
            fis.close(); 
            ois.close(); 
        } 
        catch (IOException e) 
        { 
            // Handle the exeception
            // Unable to Load map.
            // Create the memory map.
            UrlServicesUtils.logMessage(this,
                "URLProvider:loadSubscriberInfo: Exception occured ... " + e, 
                getDebugLevel()); 
            createMemoryMap(); 
        } 
        
        catch (ClassNotFoundException ce) 
        { 
            // Handle the exeception
            // Unable to Load map.
            // Create the memory map.
            UrlServicesUtils.logMessage(this,
                "URLProvider:loadSubscriberInfo: Eror loading subscriber details " + ce, 
                getDebugLevel()); 
            createMemoryMap(); 
        } 
        finally  
        { 
            try  
            { 
                if (fis != null) fis.close(); 
                if (ois != null) ois.close(); 
            } 
            catch (IOException e) 
            { 
                UrlServicesUtils.logMessage(this,"URLProvider:loadSubscriberInfo:IOException " + e,getDebugLevel()); 
            } 
        } 
    } // loadSubscriberInfo method
    
    /**
     * Helper method to store storeSubscriberInfo.
     */
    private void storeSubscriberInfo(File mapFile) 
    { 
        FileOutputStream fos = null; 
        ObjectOutputStream oos = null; 
        try  
        { 
            UrlServicesUtils.logMessage(this,"URLProvider : storeSubscriberInfo - MAP FILE = " + mapFile + " isExists " + mapFile.exists(), getDebugLevel()); 
            fos = new FileOutputStream(mapFile); 
            oos = new ObjectOutputStream(fos); 
            oos.writeObject(mProviderIDToProviderKeyMap); 
            oos.flush(); 
            fos.close(); 
            oos.close(); 
        } 
        catch (IOException e) 
        { 
            
            // Handle the exception
            // Unable to Store map.
            UrlServicesUtils.logMessage(this,"URLProvider:storeSubscriberInfo:Unable to Store map " ,getDebugLevel()); 
        } 
        finally  
        { 
            try  
            { 
                if (fos != null) fos.close(); 
                if (oos != null) oos.close(); 
            } 
            catch (IOException e) 
            { 
                UrlServicesUtils.logMessage(this,"URLProvider:storeSubscriberInfo:IOException " + e,getDebugLevel()); 
            } 
        } 
    } // storeSubscriberInfo method
    
    /**
     * Helper method to store SubscriberKey persistent.
     */
    private void createSerializedFile() 
    { 
        UrlServicesUtils.logMessage(this,"URLProvider:init:mapFilePath " + mMapFilePath,getDebugLevel()); 
        if (mMapFilePath == null) 
        { 
            
            // If it is still null. The file/file path to persist data is not available.
            // So, We can't make the map persistent.
            createMemoryMap(); 
        } 
        else  
        { 
            if (mMapFilePath.charAt(mMapFilePath.length() - 1) == File.separatorChar) 
            { 
                mCompleteMapFilePath = 
                    mMapFilePath + PROV_ID_TO_PROV_KEY_FILE_NAME; 
            } 
            else  
            { 
                mCompleteMapFilePath = 
                    mMapFilePath + File.separatorChar + PROV_ID_TO_PROV_KEY_FILE_NAME; 
            } 
            mMapFile = new File(mCompleteMapFilePath); 
            boolean fileExists = false; 
            try  
            { 
                fileExists = mMapFile.exists(); 
                if ( ! fileExists) 
                { 
                    UrlServicesUtils.logMessage(this,"URLProvider:storeSubscriberKeyPersistent: File doesn't exist. ",getDebugLevel()); 
                    boolean created = mMapFile.createNewFile(); 
                    UrlServicesUtils.logMessage(this,"URLProvider:storeSubscriberKeyPersistent: Created " + created,getDebugLevel()); 
                } 
            } 
            catch (IOException e) 
            { 
                UrlServicesUtils.logMessage(this,
                    "URLProvider:storeSubscriberKeyPersistent: " + 
                    "Exception occured ...",getDebugLevel()); 
            } 
            UrlServicesUtils.logMessage(this,"URLProvider:storeSubscriberKeyPersistent: Leaving ... ",getDebugLevel()); 
        } 
    } // storeSubscriberKeyPersistent() method
    
    /**
     * Helper method to create Memory Map for providers and subscriber keys.
     */
    private void createMemoryMap() 
    { 
        mProviderIDToProviderKeyMap = new Hashtable(); 
    } // createMemoryMap method
    
    /**
     * Helper method to parse subscriber keys using StringTokenizer.
     */
    private Vector parseSubscriberID(String str) 
    { 
        StringTokenizer st = new StringTokenizer(str,","); 
        Vector v = new Vector(); 
        while (st.hasMoreTokens()) 
        { 
            String subString = st.nextToken(); 
            v.addElement(subString); 
        } 
        return v; 
    } // parseSubscriberID
    
    /**
     * Helper method to write Subscriber info.
     */
    private void storeDetails(String fileName, String statusMessage, 
        boolean isRegistered) 
    { 
        UrlServicesUtils.logMessage(this,"URLProvider:storeDetails: Entered ...",getDebugLevel()); 
        UrlServicesUtils.logMessage(this,"URLProvider:storeDetails: Subscriber = " + this.getSubscriber(),getDebugLevel()); 
        String subscriberKey = this.getSubscriber().getId(); 
        UrlServicesUtils.logMessage(this,"URLProvider:storeDetails:subscriberKey " + subscriberKey,getDebugLevel()); 
        if (subscriberKey != null) 
        { 
            UrlServicesUtils.logMessage(this,"URLProvider:storeDetails:subscriberKey " + subscriberKey,getDebugLevel()); 
            mSubscriberMapFilePath = getProviderRepositoryPath() + File.separatorChar + subscriberKey; 
            File providerFolder = new File(mSubscriberMapFilePath); 
            if ( ! providerFolder.exists()) 
            { 
                providerFolder.mkdir(); 
            } 
            mSubscriberFile = new File( providerFolder.toString() + providerFolder.separator + fileName); 
            UrlServicesUtils.logMessage(this,
                "URLProvider:storeDetails:completeMapFilePath " + 
                mCompleteMapFilePath,getDebugLevel()); 
            try  
            { 
                OutputStreamWriter fw = 
                    new OutputStreamWriter(
                        new FileOutputStream(mSubscriberFile.toString()), "UTF-8");
                if (isRegistered) 
                { 
                    fw.write("{Timestamp}\t\t\t\t"); 
                    fw.write("{username}\t"); 
                    fw.write("{ProviderId}\t"); 
                    fw.write("{portletID}\t"); 
                    fw.write("{status}"); 
                    fw.write("\n"); 
                } 
                fw.write("\n------------ " + statusMessage + " ---------------"); 
                fw.write("\n"); 
                fw.close(); 
            } 
            catch (IOException e) 
            { 
                // File doesn't exist.
                UrlServicesUtils.logMessage(this,"URLProvider:storeDetails:Exception " + e,getDebugLevel()); 
            } 
        } // checking for subscriber key
    } // storeDetails() method
    
    /**
     ***********************************************************************
     *                                                                     *
     *             START OF URLSERVICES COMPLEX TYPE TAGS ACCESSOR METHODS *                                                                                             *
     *                                                                     *
     ***********************************************************************
     */
    
    /**
     * Set method for <proxyInfo> tag in provider.xml
     *
     * @param httpProxyInfo ProxyInformation object
     */
    public void setProxyInfo(Object httpProxyInfo) throws PortletException 
    { 
        if (!(httpProxyInfo instanceof ProxyInformation)) 
        { 
            throw new PortletException(
                "Object not an instance ProxyInformation."); 
        } 
        mHttpProxyInformation = (ProxyInformation)httpProxyInfo; 
    } 
    
    /**
     * Set method for <httpsProxyInfo> tag in provider.xml
     *
     * @param httpsProxyInfo ProxyInformation object
     */
    public void setHttpsProxyInfo(Object httpsProxyInfo) throws PortletException 
    { 
        if (!(httpsProxyInfo instanceof ProxyInformation)) 
        { 
            throw new PortletException(
                "Object not an instance of ProxyInformation."); 
        } 
        mHttpsProxyInformation = (ProxyInformation)httpsProxyInfo; 
    } 
    
    /**
     * Set method for <subscriberDetail> tag in provider.xml
     * @param subscriberDetails SubscriberDetails object
     */
    public void setSubscriberDetails(Object subscriberDetails) throws PortletException 
    { 
        if (!(subscriberDetails instanceof SubscriberDetails)) 
        { 
            throw new PortletException(
                "Object not an instance of SubscriberDetails."); 
        } 
        mSubscriberDetails = (SubscriberDetails)subscriberDetails; 
    } 
    
    /**
     * Sets the authentication details,
     * @param authentication Authentication object.
     *
     */
    public void setAuthentication(Object authentication) 
    { 
        mAuthentication = (Authentication)authentication; 
    } 
    
    /**
     * Gets the Authentication Details.
     * @return Authentication object.
     *
     */
    public Object getAuthentication() 
    { 
        return mAuthentication; 
    } 
    
    /**
     * gets the https proxyInformation object
     * @return ProxyInformation object.
     *
     */
    public Object getHttpsProxyInfo() 
    { 
        return mHttpsProxyInformation; 
    } 
    
    /**
     * gets the proxyInformation object
     * @return ProxyInformation object.
     *
     */
    public Object getProxyInfo() 
    { 
        return mHttpProxyInformation; 
    } 
    
    /**
     * gets the Subscriber Information
     * @return SubscriberDetails object
     *
     */
    public Object getSubscriberDetails() 
    { 
        return mSubscriberDetails; 
    } 
    
    /**
     *************************************************************************
     *                   Utility methods for reading proxy info              *
     *                                                                       *
     *************************************************************************
     */
    
    /**
     * Reads proxy settings from XML File and sets this inforamtion on to
     * the connection instance passed as an argument.
     */
    protected void readProxyInformation(HTTPConnection loginHttpConnection) 
    { 
        //Value of <proxyHost> Tag
        String proxyHost = null; 
        
        //Value of <proxyPort> Tag
        int proxyPort = 80;
        if (mHttpProxyInformation != null) 
        { 
            //Read <httpProxyHost> tag value
            proxyHost = mHttpProxyInformation.getHttpProxyHost(); 
               
            //Read <httpProxyPort> tag value
            proxyPort = mHttpProxyInformation.getHttpProxyPort(); 
            UrlServicesUtils.logMessage(this, 
                "http.proxyHost = " + 
                proxyHost, 
                getDebugLevel()); 
            UrlServicesUtils.logMessage(this, 
                "http.proxyPort = " + 
                proxyPort, getDebugLevel()); 

            //Earlier versions of HTTPClient expect both these properties
            //to be set for a proxy.
            loginHttpConnection.setCurrentProxy(proxyHost, proxyPort);
            loginHttpConnection.setProxyServer(proxyHost, proxyPort);
        } 
    } //end of setProxyInformation
    
    /**
     * This method is helpful in checking if the login is really successful. 
     *
     * It takes a list of error messages seperated by commas 
     * (ex: invalid login, login failed, try again ...) and parses the url
     * content to see if these messages are present. If they are found, 
     * login is assumed to be unsuccessful and vice versa.</p>
     *
     * @param urlContent HTML Contents of the specified url
     * @param errorMessage Vector of error messages
     * @return boolean Indicating the success of login.
     *
     */
    private boolean isLoginSuccessful(Provider provider,String urlContent, 
        Vector vErrorMessages, 
        int debugLevel) 
    { 
        UrlServicesUtils.logMessage(provider,
            "isLoginSuccessful : Entering isLoginSuccessful ....",
            debugLevel,
            UrlServicesUtils.BASIC_LOGGING); 
        UrlServicesUtils.logMessage(provider,
            "isLoginSuccessful : Content to be parserd for " +
            "checking login failure ***********\n" + 
            urlContent,
            debugLevel,
            UrlServicesUtils.HTML_CONTENTS_LOGGING); 
        
        //Convert the url content to uppercase.
        String lowerUrlContent = urlContent.toLowerCase(); 
        
        //Extract all tokens to see if the specified messages exist in the url content.
        for(int i=0; i<vErrorMessages.size(); i++)
        { 
            String message = (String)vErrorMessages.elementAt(i);
            
            //Convert the message to uppercase.
            message = message.toLowerCase(); 
            UrlServicesUtils.logMessage(provider,
                "UrlServicesUtils : Message to be searched is : <" + message 
                + "> ....",debugLevel); 
            
            //If the urlcontent contains the given error message, 
            //then return false logging appropriate message.
            if (lowerUrlContent.indexOf(message) !=- 1) 
            { 
                UrlServicesUtils.logMessage(provider,"UrlServicesUtils : Found error message match : <" + message + "> ....",debugLevel); 
                return false; 
            } 
        } 
        
        //return true if the specified error messages don't exist.
        UrlServicesUtils.logMessage(provider,"UrlServicesUtils : No match found. Leaving isLoginSuccessful ....",debugLevel, UrlServicesUtils.BASIC_LOGGING); 
        return true; 
    } //end of isLoginSuccessful.
    
    /**
     * Method reads parameters specified in portal's external application
     * through ExternalPrincipal
     * (@see oracle.portal.provider.v1.ExternalPricipal) object, makes name
     * value pairs out of them and returns the same.
     * @param externalApp ExternalPrincipal object holding external application
     *        details.
     * @param encoding encoding style of parameters 
     * @return String name/value pairs
     */
    public String makeQueryString(ExternalPrincipal externalApp, String encoding) 
        throws AuthenticationException 
    { 
        UrlServicesUtils.logMessage(this,
            "Entering makeQueryString *********************",
            getDebugLevel(),
            UrlServicesUtils.BASIC_LOGGING); 
        if (externalApp == null)
        { 
            AuthenticationException e = 
                new AuthenticationException("Please associate the web " + 
                "provider with required external application."); 
            UrlServicesUtils.logMessage(this,
                "makeQueryString:Please associate the web provider with " + 
                "required external application.",
                getDebugLevel()); 
            throw e; 
        } 
        if(externalApp.getName() == null)
        {
            String msg = "Please login to view the portlet.";
            AuthenticationException e = new AuthenticationException(msg);
            UrlServicesUtils.logMessage(this, msg, getDebugLevel()); 
            throw e; 
        }
            
        //Read login information
        //Read login url, username, password field names and get the 
        //corresponding values from ExternalPrincipal
        String userName = externalApp.getName(); 
        String userPasswd = externalApp.getPassword(); 
        String loginUrl = externalApp.getAuthUrl(); 
        if (loginUrl == null || loginUrl.length() == 0) 
        { 
            UrlServicesUtils.logMessage(this,
                "makeQueryString : Login URL Cannot be null.",
                getDebugLevel()); 
            throw new AuthenticationException("Login URL cannot be null. " + 
                "Please contact your administrator."); 
        } 
        UrlServicesUtils.logMessage(this,"makeQueryString : Login Url = " + 
            loginUrl, getDebugLevel()); 
        String userFieldName = externalApp.getUserFieldName(); 
        String passwdFieldName = externalApp.getPasswordFieldName(); 
        SimpleStringBuffer queryString = new SimpleStringBuffer(50); 
        try
        {
            // append encoded user name
            queryString.append(MultiByteEncoder.encode(userFieldName, encoding));
            queryString.append("=");
            queryString.append(MultiByteEncoder.encode(userName, encoding));

            queryString.append("&");
            
            // append encoded user password
            queryString.append(MultiByteEncoder.encode(passwdFieldName, encoding));
            queryString.append("=");
            queryString.append(MultiByteEncoder.encode(userPasswd, encoding));

            // append encoded additional fields
            Enumeration fieldNames = externalApp.getFields(); 
            if (fieldNames != null) 
            { 
                //Get the optional field names and their corresponding values.
                //Check if they exist.
                String fieldName = null; 
                String fieldValue = null; 
                while (fieldNames.hasMoreElements()) 
                { 
                    fieldName = (String)fieldNames.nextElement(); 
                    UrlServicesUtils.logMessage(this,
                        "makeQueryString : field name --- : " + 
                        fieldName,getDebugLevel()); 
                    fieldValue = externalApp.getField(fieldName); 
                    UrlServicesUtils.logMessage(this,
                        "makeQueryString : field value --- : " + 
                        fieldValue,getDebugLevel()); 
                    queryString.append("&");
                    queryString.append(
                        MultiByteEncoder.encode(fieldName, encoding));
                    queryString.append("=");
                    queryString.append(
                        MultiByteEncoder.encode(fieldValue, encoding));
                } 
            } 
        }
        catch(UnsupportedEncodingException ue)
        {
        }
        return queryString.toString();
    } 


    /**
     * extractCookies method is a private helper method which does following
     * Logs into url server with the Login URL & supplied username and password,
     * retrieving the cookies and saves them for future usage
     *
     * @param session   ServletProviderSession to be used to register
     *                  HttpSessionBindingListener for HttpSession.
     * @param mLoginURL The string which contains a URL to be connected for 
     *                  getting login cookies
     * @param params    Array of name value pairs fetched from External 
     *                  Application.
     * @param requestType
     *                  FORM submit type to be used while connecting to 
     *                  the login url.
     * @returns boolean Login Success/Failure
     */
    private boolean extractCookies(final ServletProviderSession session,
        String mLoginURL,
        String params,
        String requestType)
    { 

        final Provider provider = this;
        UrlServicesUtils.logMessage(this,
            "Entering extractCookies *********************",
            getDebugLevel(),
            UrlServicesUtils.BASIC_LOGGING); 

        // log on to the specified Login url of Site and get the cookie.
        try  
        { 

            //Login URL object
            URL loginURLObj = null; 
            HTTPConnection loginHttpConnection = null; 
           
            try  
            { 
                // Create an instance of Login URL
                loginURLObj = new URL(mLoginURL); 
            } 
            catch (MalformedURLException e) 
            { 
                UrlServicesUtils.logMessage(this,
                    "Exception thrown ---",e,getDebugLevel()); 
            } 
            UrlServicesUtils.logMessage(this,
                "extractCookies : Created URL Object ...",getDebugLevel()); 
            
            // Open Connection to Login URL
            loginHttpConnection = new HTTPConnection(loginURLObj); 

            //Update HTTPConnection's proxy settings.
            readProxyInformation(loginHttpConnection);
            loginHttpConnection.setContext(session.getId());
            UrlServicesUtils.logMessage(this,
                "extractCookies : Opened HTTPConnection --- " + 
                loginHttpConnection,
                getDebugLevel()); 
            HTTPResponse response = null;
            if(requestType.equalsIgnoreCase("Post"))
            {
                response = 
                    loginHttpConnection.Post(loginURLObj.getFile(),params);
            }
            else
            {
                response = 
                    loginHttpConnection.Get(loginURLObj.getFile(),params);
            }
           
            // Print HTML Header Contents
            printHtmlHeader(response); 
            Enumeration enum = response.listHeaders();
            
            //Populate cookie array.
            Cookie[] myCookies = CookieModule.listAllCookies(session.getId()); 
            UrlServicesUtils.logMessage(this, 
                "Retrieved cookie count = " + myCookies.length, getDebugLevel());
            
            //if no cookies are returned login is supposed to be failed. 
            //Return false.
            if ( myCookies == null || myCookies.length == 0 ) 
            { 
                UrlServicesUtils.logMessage(this,
                    "extractCookies: You have no cookies retrieved from portal",
                    getDebugLevel(),
                    UrlServicesUtils.BASIC_LOGGING); 
                return false; 
            } 
            //Parse the page returned to see if the error messages occur.
            // If one of the given error messages is found return false.
            return checkLogin(response); 
            
        } // End of try block
        catch (IOException ie) 
        { 
            UrlServicesUtils.logMessage(this,
                "extractCookies:IOException encountered",ie,getDebugLevel()); 
        } 
        catch (ModuleException me) 
        { 
            UrlServicesUtils.logMessage(this,
                "extractCookies:IOException encountered",me,getDebugLevel()); 
        } 
        
        // if it reaches here, some exception has occured. 
        // So return false indicating that login is failed.
        return false; 
    } 

    // Method showing headers available with HTTPResponse.
    private void printHtmlHeader(HTTPResponse response) 
        throws ModuleException, IOException
    {

      UrlServicesUtils.logMessage(this,
          "Entering printHtmlHeader *********************",
          getDebugLevel(),UrlServicesUtils.BASIC_LOGGING);

      Enumeration e = response.listHeaders();
      String headerName;
      UrlServicesUtils.logMessage(this,
          "printHtmlHeader: HEADER (KEY,VALUE) pairs ---", 
          getDebugLevel(),
          UrlServicesUtils.BASIC_LOGGING);
      while (e.hasMoreElements())
      {
          headerName = (String)e.nextElement();
          UrlServicesUtils.logMessage(this,
              headerName + ":"+response.getHeader(headerName),
              getDebugLevel(),UrlServicesUtils.BASIC_LOGGING);
      }
    }
    /**
     * Method to check if the login is successful.
     *
     * Once the user information is submitted to the loginURL, 
     * content is read from the resulting HTTPResponse. This content is
     * parsed to check for the specific occurences of &lt;errorPageMessages>
     * defined in provider.xml file. Login is considered to have failed
     * if any one of these string are found in the content.
     *
     * @param response HTTPResponse fetched from HTTPConnection.
     * @return boolean true/false based on login sucess.
     */
    private boolean checkLogin(HTTPResponse response) throws ModuleException
    {
        if(mAuthentication.getErrorPageMessages() == null)
        {
            return true;
        }
        try
        {
            //Parse the contents to check if the login is successful.
            InputStream in = response.getInputStream();
            String contentType = response.getHeader("Content-Type");
            String encoding = contentType == null ?
                UrlServicesUtils.DEFAULT_CHARACTER_ENCODING : 
                UrlServicesUtils.getEncodingFromContentType(contentType);

            StringBuffer urlContents = new StringBuffer();

            // Read from the input stream using appropriate character encoding
            BufferedReader br = 
                new BufferedReader(new InputStreamReader(in, encoding));
            String line = null;
            while( (line = br.readLine()) != null )
            {
                urlContents.append(line);
                urlContents.append(System.getProperty("line.separator"));
            }

            boolean loginResult = 
                isLoginSuccessful(this,
                    urlContents.toString(), 
                    mAuthentication.getErrorPageMessages(),
                    getDebugLevel()); 
            br.close();
            return loginResult;
        }
        catch (MalformedURLException me)
        {
            UrlServicesUtils.logMessage(this,
                "checkLogin:Invalid URL supplied for login",me,getDebugLevel());
            me.printStackTrace();
        }
        catch (IOException ie)
        {
             UrlServicesUtils.logMessage(this,
                 "checkLogin:IOException encountered",ie,getDebugLevel());
             ie.printStackTrace();
        }
        return true;
    }
} 

