
package org.jsecurity.web.attr;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import static org.jsecurity.web.WebUtils.toHttp;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.beans.PropertyEditor;

/**
 * A <tt>CookieAttribute</tt> stores an object as a {@link Cookie} for access on later requests.
 *
 * @author Les Hazlewood
 * @author Peter Ledbrook
 * @since 0.2
 */
public class CookieAttribute<T> extends AbstractWebAttribute<T> {

    private static final Log log = LogFactory.getLog(CookieAttribute.class);    

    /**
     * The number of seconds in one year (= 60 * 60 * 24 * 365).
     */
    public static final int ONE_YEAR = 60 * 60 * 24 * 365;
    /**
     * This is the same value as Integer.MAX_VALUE, and while Tomcat does fine with cookie max age with this value,
     * Jetty apparently has problems with it.  If you're using Jetty, you might want to use the
     * {@link #ONE_YEAR ONE_YEAR} constant or another value.
     */
    public static final int INDEFINITE = Integer.MAX_VALUE;

    /**
     * <code>null</code>, indicating the cookie should be set on the request context root.
     */
    public static final String DEFAULT_PATH = null;
    /**
     * <code>-1</code>, indicating the cookie should expire when the browser closes.
     */
    public static final int DEFAULT_MAX_AGE = -1;
    /**
     * Default value is <code>false</code>.
     */
    public static final boolean DEFAULT_SECURE = false;

    private String path = DEFAULT_PATH;
    private int maxAge = DEFAULT_MAX_AGE;
    private boolean secure = DEFAULT_SECURE;

    public CookieAttribute() {
    }

    /**
     * Constructs a <tt>CookieAttribute</tt> using a {@link Cookie Cookie} with the specified {@link Cookie#getName() name}
     * using the request context's path and with a {@link Cookie#setMaxAge(int) maxAge} of <tt>-1</tt>, indicating the
     * Cookie will persist until browser shutdown.
     *
     * @param name the Cookie {@link Cookie#getName() name}
     */
    public CookieAttribute(String name) {
        super(name);
    }

    /**
     * Constructs a <tt>CookieAttribute</tt> using a {@link Cookie Cookie} with the specified
     * {@link Cookie#getName() name} and {@link Cookie#getPath() path}.
     *
     * <p>A <tt>null</tt> <tt>path</tt> value means the request context's path will be used by default.
     *
     * <p>The Cookie's {@link Cookie#getMaxAge() maxAge} will be <tt>-1</tt>, indicating the Cookie will persist until
     * browser shutdown.
     *
     * @param name the Cookie {@link Cookie#getName() name}
     * @param path the Cookie {@link Cookie#setPath(String) path}.
     */
    public CookieAttribute(String name, String path) {
        super(name);
        setPath(path);
    }

    /**
     * Constructs a <tt>CookieAttribute</tt> using a {@link Cookie Cookie} with the specified
     * {@link Cookie#getName() name} and {@link Cookie#getMaxAge() maxAge}.
     *
     * <p>The Cookie's {@link javax.servlet.http.Cookie#getPath() path} will be the <tt>Request</tt>'s
     * {@link javax.servlet.http.HttpServletRequest#getContextPath() context path}.
     *
     * @param name   the Cookie {@link javax.servlet.http.Cookie#getName() name};
     * @param maxAge the Cookie {@link Cookie#getMaxAge() maxAge}
     */
    public CookieAttribute(String name, int maxAge) {
        super(name);
        setMaxAge(maxAge);
    }

    /**
     * Constructs a <tt>CookieAttribute</tt> using a {@link Cookie Cookie} with the specified
     * {@link Cookie#getName() name}, {@link javax.servlet.http.Cookie#getPath() path}, and
     * {@link Cookie#getMaxAge() maxAge}.
     *
     * @param name   the Cookie {@link Cookie#getName() name}
     * @param path   the Cookie {@link Cookie#setPath(String) path}.
     * @param maxAge the Cookie {@link Cookie#getMaxAge() maxAge}
     */
    public CookieAttribute(String name, String path, int maxAge) {
        this(name, path);
        setMaxAge(maxAge);
    }

    /**
     * Constructs a <tt>CookieAttribute</tt> using a {@link Cookie Cookie} with the specified
     * {@link Cookie#getName() name}, {@link javax.servlet.http.Cookie#getPath() path}, and
     * {@link Cookie#getMaxAge() maxAge}, utilizing the specified <tt>PropertyEditor</tt> to perform value/string
     * conversion on the object stored as a cookie.
     *
     * @param name        the Cookie {@link Cookie#getName() name}
     * @param path        the Cookie {@link Cookie#setPath(String) path}.
     * @param maxAge      the Cookie {@link Cookie#getMaxAge() maxAge}
     * @param editorClass the <tt>PropertyEditor</tt> to perform value/string conversion on the object stored as a
     *                    Cookie.
     */
    public CookieAttribute(String name, String path, int maxAge, Class<? extends PropertyEditor> editorClass) {
        super(name, editorClass);
        setPath(path);
        setMaxAge(maxAge);
    }

    /**
     * Returns the Cookie's {@link Cookie#getPath() path} setting.  If <tt>null</tt>, the <tt>request</tt>'s
     * {@link javax.servlet.http.HttpServletRequest#getContextPath() context path} will be used.
     *
     * <p>The default is <code>null</code>.</p>
     *
     * @return the Cookie's path, or <tt>null</tt> if the request's context path should be used as the path when the
     *         cookie is created.
     */
    public String getPath() {
        return path;
    }

    /**
     * Sets the Cookie's {@link Cookie#getPath() path} setting.  If the argument is <tt>null</tt>, the <tt>request</tt>'s
     * {@link javax.servlet.http.HttpServletRequest#getContextPath() context path} will be used.
     *
     * <p>The default is <code>null</code>.</p>
     *
     * @param path the Cookie's path, or <tt>null</tt> if the request's context path should be used as the path when the
     *             cookie is created.
     */
    public void setPath(String path) {
        this.path = path;
    }

    /**
     * Returns the Cookie's {@link Cookie#setMaxAge(int) maxAge} setting.  Please see that JavaDoc for the semantics on
     * the repercussions of negative, zero, and positive values for the maxAge.
     *
     * <p>The default value is <code>-1</code>, meaning the cookie will expire when the browser is closed.</p>
     *
     * @return the Cookie's {@link Cookie#setMaxAge(int) maxAge}
     */
    public int getMaxAge() {
        return maxAge;
    }

    /**
     * Sets the Cookie's {@link Cookie#setMaxAge(int) maxAge} setting.  Please see that JavaDoc for the semantics on
     * the repercussions of negative, zero, and positive values for the maxAge.
     *
     * <p>The default value is <code>-1</code>, meaning the cookie will expire when the browser is closed.</p>
     *
     * @param maxAge the Cookie's {@link Cookie#setMaxAge(int) maxAge}
     */
    public void setMaxAge(int maxAge) {
        this.maxAge = maxAge;
    }

    public boolean isSecure() {
        return secure;
    }

    public void setSecure(boolean secure) {
        this.secure = secure;
    }

    /**
     * Returns the cookie with the given name from the request or <tt>null</tt> if no cookie
     * with that name could be found.
     *
     * @param request    the current executing http request.
     * @param cookieName the name of the cookie to find and return.
     * @return the cookie with the given name from the request or <tt>null</tt> if no cookie
     *         with that name could be found.
     */
    private static Cookie getCookie(HttpServletRequest request, String cookieName) {
        Cookie cookies[] = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(cookieName)) {
                    return cookie;
                }
            }
        }
        return null;
    }

    public T onRetrieveValue(ServletRequest request, ServletResponse response) {
        T value = null;

        String stringValue;
        Cookie cookie = getCookie(toHttp(request), getName());
        if (cookie != null) {
            stringValue = cookie.getValue();
            if (log.isInfoEnabled()) {
                log.info("Found string value [" + stringValue + "] from HttpServletRequest Cookie [" + getName() + "]");
            }
            value = fromStringValue(stringValue);
        } else {
            if (log.isDebugEnabled()) {
                log.debug("No value found in request Cookies under cookie name [" + getName() + "]");
            }
        }

        return value;
    }

    public void onStoreValue(T value, ServletRequest servletRequest, ServletResponse servletResponse) {

        HttpServletRequest request = toHttp(servletRequest);
        HttpServletResponse response = toHttp(servletResponse);

        String name = getName();
        int maxAge = getMaxAge();
        String path = getPath() != null ? getPath() : request.getContextPath();

        String stringValue = toStringValue(value);
        Cookie cookie = new Cookie(name, stringValue);
        cookie.setMaxAge(maxAge);
        cookie.setPath(path);
        if (isSecure()) {
            cookie.setSecure(true);
        }

        response.addCookie(cookie);
        if (log.isTraceEnabled()) {
            log.trace("Added Cookie [" + name + "] to path [" + path + "] with value [" +
                    stringValue + "] to the HttpServletResponse.");
        }
    }

    public void removeValue(ServletRequest servletRequest, ServletResponse response) {
        HttpServletRequest request = toHttp(servletRequest);
        Cookie cookie = getCookie(request, getName());
        if (cookie != null) {
            cookie.setMaxAge(0);
            //JSEC-94: Must set the path on the outgoing cookie (some browsers don't retain it from the
            //retrieved cookie?)
            cookie.setPath(getPath() == null ? request.getContextPath() : getPath());
            cookie.setSecure(isSecure());
            toHttp(response).addCookie(cookie);
        }
    }
}
