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

package oracle.portal.provider.v1.http;

import javax.servlet.http.*;
import java.util.*;
import java.text.*;
import java.security.*;
import java.io.UnsupportedEncodingException;

import oracle.portal.provider.v1.*;
import oracle.webdb.utils.*;
import oracle.portal.utils.v1.MultiByteDecoder;

/**
 * Represents the Portal user.  

 * Additionally provides the methods for establishing and reaquiring the 
 * user's session. This class used to be stored in the HttpSession but now it
 * accesses the ServletProviderSession on an as-needed basis to prevent memory
 * leaks.
 * 
 * Some Providers have a distinct notion users. Providers are able to configure
 * the Portal (using a login server) to map between the Portal user and the
 * application (Provider) user.   The ProviderUser interface provides access to
 * both the Portal user information and this mapped information.<p>
 * The portal runs in a default public mode.  This occurs when a user accesses
 * the portal without logging in.  This is represented by a special ProviderUser
 * whose portal user name is defined by the String ProviderUser.PUBLIC.  The
 * convenience method isPublicUser() is provided to simplify this check.
 *
 * Note: since this object retains a reference to the HttpRequest, it should not
 *       be placed in the HttpSession (or ProviderSession). Each request that is
 *       potentially user-based includes information to create a new user object
 *      
 */
public class ServletProviderUser extends ProviderUser 
{

    /**
     * Inner class -- representing the Principal
     */
    private class ProviderPrincipal implements Principal
    {
        private String mName;
        private String mSubscriberId;
        private String mCompanyName;

        /*
         *  Cosntructor
         */
        public ProviderPrincipal(String name,
                                 String subscriberId,
                                 String companyName)
        {
            mName = name;
            mSubscriberId = subscriberId;
            mCompanyName = companyName;
        }

        /**
         *  tests whether this object represents the same user as the passed object.
         *  @return true if the portal user name's are the same (case-sensitive).
         */
        public boolean equals(Object another)
        {
            if (!(another instanceof Principal)) return false;

            Principal test = (Principal) another;

            return mName.equals(test.getName());
        }


        /**
         *  @return a string representation of this User.  Currently the portal user name.
         */
        public String toString()
        {
            return mName;
        }

        /**
         *  @return a hashcode representing this user
         */
        public int hashCode()
        {
            return mName.hashCode();
        }

        /**
         *  @return the portal user name
         */
        public String getName()
        {
            return mName;
        }

        /**
         *  @return the subscriber id
         */
        public String getSubscriberId()
        {
            return mSubscriberId;
        }

        /**
        *  @return the company name  name
        */
        public String getCompanyName()
        {
            return mCompanyName;
        }
    }


    // ProviderUser methods start here .....

    /*
     * Constructor
     *
     * This version of the constructor is for use in SHOW calls where
     * the parameters must be decoded using the multibyte decoder. This is
     * slow so the parameters are only requested if they are needed.
     */
    public ServletProviderUser(ServletProviderRequest request,
                               int servletSessionTimeout)
    {
        mProviderRequest = request;
        mHttpRequest = mProviderRequest.getRequest();
        mServletSessionTimeout = servletSessionTimeout;

        // expecting a strict format
        sLocationTimeFormat.setLenient(false);
    }

    /**
    * Constructor
    *
    * This version of the constructor is used for creating a user object
    * from a SOAP message. The information is all available in the soap
    * message and does not need to be decoded
    *
    * User location is always sent as Http Headers so it is never passed
    * into the constructor. It will be read and constructed as needed.
    */
    public ServletProviderUser(ServletProviderRequest request,
                               String name308,
                               String name309,
                               String subscriberId,
                               String companyName,
                               int authenticationLevel,
                               String mappedUserName,
                               String loginTime,
                               String sessionId,
                               int servletSessionTimeout)
    {
        this(request, servletSessionTimeout);

        mName308 = name308;
        mName309 = name309;
        mSubscriberId = subscriberId;
        mCompanyName = companyName;
        mAuthenticationLevel = authenticationLevel;
        mMappedName = mappedUserName;
        mLoginTimeString = loginTime;
        mPortalSessionId = sessionId;
        mServletSessionTimeout = servletSessionTimeout;
    }

    /**
     *  @return the portal user.
     */
    public Principal getPrincipal()
    {
        if (mUser != null)
            return mUser;

        mUser = new ProviderPrincipal(getName(),
                                      getSubscriberId(),
                                      getCompanyName());
        return mUser;
    }

    /**
     * @return the mapped application user.
     * <b>null</b> if there is no mapped user.
     *
     */
    public Principal getMappedPrincipal()
    {
        if (mMappedUser != null)
            return mMappedUser;

        mMappedUser = new ProviderPrincipal(getMappedName(), null, null);
        return mMappedUser;
    }

    /**
     *  @return the portal username. Convenience method for getPrincipal().getName()
     */
    public String getName()
    {
        if (mName != null)
            return mName;

        // name308 is the username as sent by Portal release 3.0.8 and earlier
        // This variable communicates 2 pieces of information, the logged-in
        // state of the user and the username. In release 3.0.9, the logged in
        // state is communicated via the authentication level and the name309
        // communicates the username.
        // Two variables are necessary because in release 3.0.9 and later, the
        // username should always represent the actual username of the user - it
        // will never be "portal_public".  This means that a second variable was
        // necessary to maintain backwards compatibility when a provider written
        // using JPDK 3.0.9 or later is registered with portal 3.0.8 or earlier

        // use name309 if it is given to us, otherwise use name308.

        mName = mProviderRequest.getUser();

        return mName;
    }

    /**
     *  @return the subscriber id. Convenience method for getPrincipal().getSubscriberId()
     */
    public String getSubscriberId()
    {
        if (mSubscriberId != null)
            return mSubscriberId;

        mSubscriberId = mProviderRequest.getSubscriberId();
        return mSubscriberId;
    }

    /**
     *  @return the company name. Convenience method for getPrincipal().getCompanyName()
     */
    public String getCompanyName()
    {
        if (mCompanyName != null)
            return mCompanyName;

        mCompanyName = mProviderRequest.getCompanyName();
        return mCompanyName;
    }

    /**
     *  @return the mapped application username.
     *  Convenience method for getMappedPrincipal().getName()
     */
    public String getMappedName()
    {
        if (mMappedName != null)
            return mMappedName;

        mMappedName = mProviderRequest.getMappedUser();
        return mMappedName;
    }

   /**
     *  @return the qulaified username, ie username appended with company name
     *  separator is "@@"
     */
    public String getQualifiedName()
    {
        if (mQualifiedName != null)
            return mQualifiedName;

        SimpleStringBuffer qualName = new SimpleStringBuffer(getName());
        qualName.append("@@").append(getCompanyName());
        mQualifiedName = qualName.toString();
        return mQualifiedName;
    }


    /**
     *  @return the time when this user logged into the Portal
     */
    public Calendar getLoginTime()
    {
        if (mLoginTime != null)
            return mLoginTime;

        if (mLoginTimeString == null)
            mLoginTimeString = mProviderRequest.getLoginTime();

        if (mLoginTimeString == null)
            return null;

        // Parse the login time with the new extended date / time
        // format. If that doesn't succeed, try the old one.
        Date d = null;
        try
        {
            d = sLoginTimeFormat.parse(mLoginTimeString);
        }
        catch (ParseException pe)
        {
            try
            {
                d = sLoginTimeOldFormat.parse(mLoginTimeString);
            }
            catch (ParseException pe2)
            {
                d = null;
            }
        }

        if (d != null)
        {
            mLoginTime = new GregorianCalendar();
            mLoginTime.setTime(d);
        }

        return mLoginTime;
    }

    /**
     *  @return the Portal session ID for this user.  Portlet developers can use
     *  this ID to map to their in memory "session" state vs. using a cookie based
     *  mechanism.  The benefit is performance.  If the provider doesn't use cookies
     *  the portal can communicate more efficiently with it.
     */
    public String getPortalSessionId()
    {
        if (mPortalSessionId != null)
            return mPortalSessionId;

        mPortalSessionId = mProviderRequest.getSessionId();
        return mPortalSessionId;
    }

    /**
    * @return the authentication level for the user
    */
    public int getAuthenticationLevel()
    {
        if (mAuthenticationLevel != AUTH_NOT_DETERMINED)
            return mAuthenticationLevel;

        // initialize the authentication level
        mAuthenticationLevel = mProviderRequest.getAuthenticationLevel();

        if (mAuthenticationLevel == AUTH_LEVEL_NOT_SUPPORTED)
        {
            // authLevel is not sent by portal, therefore it must be portal
            // 3.0.8 or earlier.  Derive the authentication level from
            // name308
            String name308 = getName308();
            if (name308 == null)
                // should never happen - but saves checking for null in
                // subsequent IF statements
                mAuthenticationLevel = NOT_AUTHENTICATED;
            else if (name308.equalsIgnoreCase(PORTAL_PUBLIC))
                mAuthenticationLevel = NOT_AUTHENTICATED;
            else if (name308.equalsIgnoreCase(PUBLIC_USERNAME))
                mAuthenticationLevel = PUBLIC_STRONG_AUTH;
            else
                mAuthenticationLevel = USER_STRONG_AUTH;
        }
        return mAuthenticationLevel;
    }

    /**
    * @return true if the user has logged onto the portal in the current session.
    *  A user who is weakly authentication has not logged on to the portal in the
    *  current session so this method will return false.
    */
    public boolean isLoggedOn()
    {
        return getAuthenticationLevel() > 5 ? true : false;
    }

    /**
    * @return true if the current user is at least weakly authenticated
    */
    public boolean isWeaklyAuthenticated()
    {
        return getAuthenticationLevel() > 0 ? true : false;
    }

    /**
     *  Tests whether we are running in public mode.  ie the user has not
     *  logged in and is not weakly authenticated based on a prior login
     *  @return true if the user is accessing the portal in public mode.
     */
    public boolean isPublicUser()
    {
        return (getAuthenticationLevel() == NOT_AUTHENTICATED);
    }


    /**
     * gets a reference to the attached ServletProviderSession object.
     * 
     * Optionally create a new ServletProviderSession if create = true.
     * 
     * @param create create a new ServletProviderSession if one is not 
     *        already associated with the current request
     * @return attached ServletProviderSession or NULL if this request is not 
     *         associated with an active ServletProviderSession and "create"
     *         is false
     */ 
    public ProviderSession getSession(boolean create) throws IllegalStateException 
    {
        return ServletProviderSession.getAttachedSession(mHttpRequest,
                                                         create,
                                                         mServletSessionTimeout);
    }


    /**
     * gets a reference to the attached ServletProviderSession object.
     * 
     * @return attached ServletProviderSession or NULL if this request is not
     *         associated with an active ServletProviderSession
     */ 
    public ProviderSession getSession() throws IllegalStateException 
    {
        return ServletProviderSession.getAttachedSession(mHttpRequest);
    }


    public UserLocation getLocation()
    {
        String addressLine1;

        if (mLocation != null)
        {
            return mLocation;
        }
        else if (mCheckLocationHeaders)
        {
            // set to false so we don't have to keep looking at the headers
            // each time this method is called.
            mCheckLocationHeaders = false;

            // Look for the first address line header. If the header is not
            // found, assume there is no address information.
            addressLine1 = mHttpRequest.getHeader(
                HttpProviderConstants.IASWIRELESS_USER_LOCATION_ADDRESSLINE1_HEADER);

            if (addressLine1 == null)
            {
                return null;
            }
        }
        else
        {
            return null;
        }


        // now get the rest of the data
        String addressLine2 = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_ADDRESSLINE2_HEADER);
        String addressLineLast = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_ADDRESSLASTLINE_HEADER);
        String block = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_BLOCK_HEADER);
        String city = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_CITY_HEADER);
        String county = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_COUNTY_HEADER);
        String state = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_STATE_HEADER);
        String country = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_COUNTRY_HEADER);
        String postalCode = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_POSTALCODE_HEADER);
        String postalCodeExt = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_POSTALCODEEXT_HEADER);
        String type = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_TYPE_HEADER);
        String companyName = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_COMPANY_HEADER);
        String positionX = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_X_HEADER);
        String positionY = mHttpRequest.getHeader(
            HttpProviderConstants.IASWIRELESS_USER_LOCATION_Y_HEADER);

        // Determine how these values are encoded
        String encoding = mHttpRequest.getHeader(HttpProviderConstants.HTTP_PORTAL_CONTENT_CHARSET);
        if (encoding == null)
        {
            encoding = HttpProviderConstants.HTTP_DEFAULT_CHARSET;
        }

        // create the address object from above info
        // first decode the values as they are encoded like URL parameters
        UserLocation.Address address = null;
        try {
            address = new UserLocation.Address(
                MultiByteDecoder.decode(companyName, encoding),
                MultiByteDecoder.decode(addressLine1, encoding),
                MultiByteDecoder.decode(addressLine2, encoding),
                MultiByteDecoder.decode(addressLineLast, encoding),
                MultiByteDecoder.decode(block, encoding),
                MultiByteDecoder.decode(city, encoding),
                MultiByteDecoder.decode(county, encoding),
                MultiByteDecoder.decode(state, encoding),
                MultiByteDecoder.decode(country, encoding),
                MultiByteDecoder.decode(postalCode, encoding),
                MultiByteDecoder.decode(postalCodeExt, encoding));
        }
        catch (UnsupportedEncodingException uee)
        {
            return null;
        }

        // convert the values that need converting
        UserLocation.LocationType lType =
            UserLocation.LocationType.getLocationType(type);

        float x = 0, y = 0;
        try
        {
            if (positionX != null) x = Float.valueOf(positionX).floatValue();
        }  catch (NumberFormatException nfe) {}; // ignore as already set to default.
        try
        {
            if (positionY != null) y = Float.valueOf(positionY).floatValue();
        }  catch (NumberFormatException nfe) {}; // ignore as already set to default.

        mLocation = new UserLocation(lType, x, y, address);
        return mLocation;
    }

    /**
     *  Gets an array of all the cookies established between this user and the
     *  provider, or a <code>null</code> value if the user currently has no
     *  cookies. Cookies must be established in the Provider's initSession
     *  method.
     *
     *  @return an array of all this user's cookies or <code>null</code> if the
     *  user currently has no cookies.
     */
    public Cookie[] getCookies()
    {
        return mHttpRequest.getCookies();
    }

    /**
     *  @return the 308 version of the portal username.
     */
    private String getName308()
    {
        if (mName308 != null)
            return mName308;

        mName308 = mProviderRequest.getUser308();
        return mName308;
    }

    /**
     *  @return the 308 version of the portal username.
     */
    private String getName309()
    {
        if (mName309 != null)
            return mName309;

        mName309 = mProviderRequest.getUser309();
        return mName309;
    }



    // static constants defining various date/time formats
    private static final DateFormat sLoginTimeFormat = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
    private static final DateFormat sLoginTimeOldFormat = new SimpleDateFormat("yyyy-MM-dd");
    private static final DateFormat sLocationTimeFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");

    private ServletProviderRequest mProviderRequest;
    private HttpServletRequest mHttpRequest;
    private Cookie[] mCookies;

    private String mName = null;
    private String mName308 = null;
    private String mName309 = null;
    private String mMappedName = null;
    private String mSubscriberId = null;
    private String mCompanyName = null;
    private String mQualifiedName = null;
    private String mLoginTimeString = null;
    private String mPortalSessionId = null;
    private int mAuthenticationLevel = AUTH_NOT_DETERMINED;
    private int mServletSessionTimeout;
    private boolean mCheckLocationHeaders = true;

    // the object represenations below are created only if needed
    private Principal mUser = null;
    private Principal mMappedUser = null;
    private Calendar mLoginTime = null;
    private UserLocation mLocation = null;

}

