/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Apache License,
// Version 2.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.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
package org.olap4j.driver.xmla.proxy;

import java.net.URL;
import java.net.URLConnection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <p>CookieManager is a simple utility for handling cookies when working
 * with <code>java.net.URL</code> and <code>java.net.URLConnection</code>
 * objects.
 *
 * <p>This code was taken from http://www.hccp.org/java-net-cookie-how-to.html
 * and modified by Luc Boudreau to support concurrent access and be more
 * thread safe.
 *
 * <p>Here's a few use cases examples...
 *
 * <hr>
 * <code>
 *     <p>Cookiemanager cm = new CookieManager();
 *     <p>URL url = new URL("http://www.hccp.org/test/cookieTest.jsp");
 *
 *     <p> . . .
 *
 *     <p>// getting cookies:
 *     <p>URLConnection conn = url.openConnection();
 *     <p>conn.connect();
 *
 *     <p>// setting cookies
 *     <p>cm.storeCookies(conn);
 *     <p>cm.setCookies(url.openConnection());
 * </code>
 * <hr>
 *
 * @author <a href="mailto:spam@hccp.org">Ian Brown</a>
 */

@SuppressWarnings("unchecked")
class XmlaOlap4jCookieManager {

    private Map store;

    private static final String SET_COOKIE = "Set-Cookie";
    private static final String COOKIE_VALUE_DELIMITER = ";";
    private static final String PATH = "path";
    private static final String EXPIRES = "expires";
    private static final String DATE_FORMAT = "EEE, dd-MMM-yyyy hh:mm:ss z";
    private static final String SET_COOKIE_SEPARATOR = "; ";
    private static final String COOKIE = "Cookie";

    private static final char NAME_VALUE_SEPARATOR = '=';
    private static final char DOT = '.';

    private DateFormat dateFormat;

    private boolean debug = false;

    public XmlaOlap4jCookieManager() {
        store = new ConcurrentHashMap();
        dateFormat = new SimpleDateFormat(DATE_FORMAT);
    }

    /**
     * Retrieves and stores cookies returned by the host on the other side
     * of the the open java.net.URLConnection.
     *
     * The connection MUST have been opened using the <i>connect()</i>
     * method or a IOException will be thrown.
     *
     * @param conn a java.net.URLConnection - must be open,
     * or IOException will be thrown
     * @throws java.io.IOException Thrown if <i>conn</i> is not open.
     */
    public void storeCookies(URLConnection conn) {
        // Determines the domain from where these cookies are being sent
        String domain = getDomainFromHost(conn.getURL().getHost());

        Map domainStore; // Where we will store cookies for this domain

        // Checks the store to see if we have an entry for this domain
        if (store.containsKey(domain)) {
            // we do, so lets retrieve it from the store
            domainStore = (Map) store.get(domain);
        } else {
            // we don't, so let's create it and put it in the store
            domainStore = new ConcurrentHashMap();
            store.put(domain, domainStore);
        }

        // OK, now we are ready to get the cookies out of the URLConnection

        String headerName = null;
        for (int i = 1; (headerName = conn.getHeaderFieldKey(i)) != null; i++) {
            if (headerName.equalsIgnoreCase(SET_COOKIE)) {
                Map cookie = new ConcurrentHashMap();
                StringTokenizer st = new StringTokenizer(
                    conn.getHeaderField(i), COOKIE_VALUE_DELIMITER);

                // the specification dictates that the first name/value pair
                // in the string is the cookie name and value, so let's handle
                // them as a special case:

                if (st.hasMoreTokens()) {
                    String token = st.nextToken();
                    String name = token.substring(
                        0,
                        token.indexOf(NAME_VALUE_SEPARATOR));
                    String value =
                        token.substring(
                            token .indexOf(NAME_VALUE_SEPARATOR) + 1,
                            token.length());
                    domainStore.put(name, cookie);
                    cookie.put(name, value);

                    if (this.debug) {
                        System.out.println(
                            "Saving cookie : " + name + "=" + value);
                    }
                }

                while (st.hasMoreTokens()) {
                    String token = st.nextToken();

                    // Check if the separator does exist
                    // The other attributes are not stored (Ex: HttpOnly)
                    int separatorIndex = token.indexOf(NAME_VALUE_SEPARATOR);

                    if (separatorIndex > 0) {
                        String tokenName =
                            token.substring(
                                0,
                                separatorIndex)
                            .toLowerCase();
                        String tokenValue =
                            token.substring(
                                separatorIndex + 1,
                                token.length());

                        cookie.put(tokenName, tokenValue);

                        if (this.debug) {
                            System.out.println(
                                "Saving cookie : "
                                + tokenName
                                + "=" + tokenValue);
                        }
                    }
                }
            }
        }
    }

    /**
     * Prior to opening a URLConnection, calling this method will set all
     * unexpired cookies that match the path or subpaths for thi underlying URL.
     *
     * <p>The connection MUST NOT have been opened method or an IOException will
     * be thrown.
     *
     * @param  conn a java.net.URLConnection  - must  NOT be  open, or
     * IOException will be thrown
     *
     * @throws java.io.IOException Thrown if <i>conn</i> has already been
     * opened.
     */
    public void setCookies(URLConnection conn) {
        // Determines the domain and path to retrieve the appropriate cookies
        URL url = conn.getURL();
        String domain = getDomainFromHost(url.getHost());
        String path = url.getPath();

        Map domainStore = (Map) store.get(domain);
        if (domainStore == null) {
            return;
        }
        StringBuffer cookieStringBuffer = new StringBuffer();

        Iterator cookieNames = domainStore.keySet().iterator();
        while (cookieNames.hasNext()) {
            String cookieName = (String) cookieNames.next();
            Map cookie = (Map) domainStore.get(cookieName);
            // check cookie to ensure path matches  and cookie is not expired
            // if all is cool, add cookie to header string
            if (comparePaths((String) cookie.get(PATH), path)
                && isNotExpired((String) cookie.get(EXPIRES)))
            {
                cookieStringBuffer.append(cookieName);
                cookieStringBuffer.append("=");
                cookieStringBuffer.append((String) cookie.get(cookieName));
                if (cookieNames.hasNext()) {
                    cookieStringBuffer.append(SET_COOKIE_SEPARATOR);
                }
            }
        }
        try {
            if (this.debug
                && !(cookieStringBuffer.toString().equals("")))
            {
                System.out.println(
                    "Using cookie : " + cookieStringBuffer.toString());
            }
            conn.setRequestProperty(COOKIE, cookieStringBuffer.toString());
        } catch (java.lang.IllegalStateException ise) {
            throw new RuntimeException(
                "Illegal State! Cookies cannot be set on a URLConnection that is already connected. Only call setCookies(java.net.URLConnection) AFTER calling java.net.URLConnection.connect().");
        }
    }

    private String getDomainFromHost(String host) {
        if (host.indexOf(DOT) != host.lastIndexOf(DOT)) {
            return host.substring(host.indexOf(DOT) + 1);
        } else {
            return host;
        }
    }

    private boolean isNotExpired(String cookieExpires) {
        if (cookieExpires == null) {
            return true;
        }
        Date now = new Date();
        try {
            return (now.compareTo(dateFormat.parse(cookieExpires))) <= 0;
        } catch (java.text.ParseException pe) {
            pe.printStackTrace();
            return false;
        }
    }

    private boolean comparePaths(String cookiePath, String targetPath) {
        if (cookiePath == null) {
            return true;
        } else if (cookiePath.equals("/")) {
            return true;
        } else if (targetPath.regionMatches(
                0, cookiePath, 0, cookiePath.length()))
        {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returns a string representation of stored cookies organized by domain.
     */

    public String toString() {
        return store.toString();
    }
}

// End XmlaOlap4jCookieManager.java



