/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
 */
package com.ericsson.ssa.sip;

import com.ericsson.ssa.config.Constants;
import com.ericsson.ssa.config.ConvergedContextImpl;
import com.ericsson.ssa.config.SipFactoryFacade;
import com.ericsson.ssa.sip.SipApplicationSessionImpl;

import org.apache.catalina.core.StandardContext;
import org.apache.catalina.session.StandardSession;
import org.apache.catalina.session.StandardSessionFacade;

import org.jvnet.glassfish.comms.util.LogUtil;

import java.net.MalformedURLException;
import java.net.URL;

import javax.servlet.ServletContext;
import javax.servlet.sip.ConvergedHttpSession;
import javax.servlet.sip.SipApplicationSession;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.SipServlet;
import javax.servlet.sip.SipSessionsUtil;

/**
 * Class representing a ConvergedHttpSession.
 *
 * @author jluehe
 */
public class ConvergedHttpSessionFacade extends StandardSessionFacade
    implements ConvergedHttpSession {

    private static final String HTTP_PROTOCOL = "http";
    private static final String HTTPS_PROTOCOL = "https";

    // The parent SipApplicationSession
    private SipApplicationSession sipAppSession;

    // The nested HTTP session for which this object acts as a facade
    private StandardSession httpSession;

    /*
     * The servlet context associated with the session manager of the session
     * for which this object acts as a facade
     */
    private ServletContext servletCtxt;

    // The SipSessionManager to look up the SipApplicationSession parent
    private SipSessionManager sipSessionManager;

    /**
     * Constructor
     */
    public ConvergedHttpSessionFacade(StandardSession httpSession,
        ConvergedContextImpl ctxt) {
        super(httpSession);
        this.httpSession = httpSession;
        this.sipSessionManager = ctxt.getSipSessionManager();
        this.servletCtxt = ctxt.getServletContext();
    }

    /**
     * Returns the parent <code>SipApplicationSession</code> if it exists,
     * if none exists then a new one is created and returned after
     * associating it with the converged http session.
     *
     * @return the parent SipApplicationSession
     */
    public SipApplicationSession getApplicationSession() {
        if (sipAppSession != null) {
            return sipAppSession;
        }

        // Check to see if our nested HTTP session already has any
        // SipApplicationSession id associated with it.
        String sipAppSessionId = httpSession.getSipApplicationSessionId();

        if (sipAppSessionId == null) {
            // Check to see if the request URL that was used to resume the
            // HTTP session contains an encoded SipApplicationSession id.
            sipAppSessionId = parseRequestParameter(
                Constants.SAS_ID_URI_PARAMETER);
        }

        if (sipAppSessionId != null) {
            // Look up the SipApplicationSession with the given id
            sipAppSession = sipSessionManager.findSipApplicationSession(
                sipAppSessionId);
        } else {
            // Create a new SipApplicationSession
            SipFactoryFacade sipFactory = (SipFactoryFacade) servletCtxt.getAttribute(
                SipServlet.SIP_FACTORY);

            if (sipFactory == null) {
                // XXX LOC
                throw new IllegalStateException("No SipFactory");
            }

            sipAppSession = sipFactory.createApplicationSessionImpl();
        }

        if ((sipAppSession != null) &&
                (httpSession.getSipApplicationSessionId() == null)) {
            // Associate the nested HTTP session with the SipApplicationSession
            httpSession.setSipApplicationSessionId(sipAppSession.getId());
            ((SipApplicationSessionImpl) sipAppSession).addSession(this);
        }

        return sipAppSession;
    }

    /**
     * Encodes the HTTP URL with the jsessionid. ";jsessionid=http-session-id".
     * The URL parameter should be an absolute URL. For example,
     * http://server:7001/mywebapp/foo.jsp. Where "/mywebapp" is the context
     * path of the the current ServletContext, because that is where the
     * httpSession belongs to.
     *
     * @param url the HTTP URL String to be encoded
     *
     * @return encoded URL with jsessionid
     */
    public String encodeURL(String url) {

        if (!isEncodeable(url)) {
            return url;
        } else {
            return toEncoded(url);
        }
    }

    /**
     * Converts the given relative path to an absolute URL by prepending
     * the contextPath for the current ServletContext, the given scheme
     * ("http" or "https"), and the host:port, and then encoding the
     * resulting URL with the jsessionid.
     *
     * For example, this method converts:
     *
     *   from: "/foo.jsp"
     *   to: "http://server:8888/mywebapp/foo.jsp;jsessionid=http-session-id"
     *
     * Where, "/mywebapp" is the contextPath for the current ServletContext
     * server is the front end host defined for the web server.
     *
     * @param relativePath relative to the current webapp
     * @param scheme the scheme ("http" or "https")
     *
     * @return encoded URL with jsessionid
     */
    public String encodeURL(String relativePath, String scheme) {
        if ((relativePath == null) || !relativePath.startsWith("/")) {
            throw new IllegalArgumentException("Illegal relative path: " +
                relativePath); // XXX LOC
        }

        if ((scheme == null) ||
                (!scheme.equals(HTTP_PROTOCOL) &&
                !scheme.equals(HTTPS_PROTOCOL))) {
            throw new IllegalArgumentException("Invalid protocol: " + scheme); // XXX LOC
        }

        String requestUrlString = (String) httpSession.getNote(
            Constants.REQUEST_URL_SESSION_NOTE);
        if (requestUrlString == null) {
            return null;
        }

        URL requestUrl = null;
        try {
            requestUrl = new URL(requestUrlString);
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }

        StringBuilder sb = new StringBuilder(scheme);
        sb.append("://");
        sb.append(requestUrl.getHost());
        sb.append(':');
        sb.append(requestUrl.getPort());
        sb.append(servletCtxt.getContextPath());
        sb.append(relativePath);

        return encodeURL(sb.toString());
    }

    /**
     * @param urlString The URL to be encoded
     *
     * @return true if the given URL needs to be encoded, false otherwise
     */
    private boolean isEncodeable(String urlString) {
        // Is the given (absolute) URL valid?
        URL url = null;

        try {
            url = new URL(urlString);
        } catch (MalformedURLException e) {
            return false;
        }

        String myContextPath = servletCtxt.getContextPath();

        if (myContextPath != null) {
            String file = url.getFile();

            // Make sure the context path in the given URL matches our own
            if ((file == null) || !file.startsWith(myContextPath)) {
                return false;
            }
            if (file.indexOf(";jsessionid=" + httpSession.getIdInternal()) >= 0) {
                return false;
            }
        }

        return true;
    }

    /**
     * Encodes the given URL with the HTTP session id and the beroute
     * (or bekey, whichever is present)
     *
     * @param url The URL to be encoded
     *
     * @return The encoded URL
     */
    private String toEncoded(String url) {
        if (url == null) {
            return url;
        }

        String path = url;
        String query = "";
        String anchor = "";

        int question = url.indexOf('?');
        if (question >= 0) {
            path = url.substring(0, question);
            query = url.substring(question);
        }

        int pound = path.indexOf('#');
        if (pound >= 0) {
            anchor = path.substring(pound);
            path = path.substring(0, pound);
        }

        StringBuilder encoded = new StringBuilder(path);
        if (encoded.length() > 0) { // Encoded params can't be first.
            encoded.append(";jsessionid=");
            encoded.append(httpSession.getIdInternal());
            String beroute = (String) httpSession.getNote(
                Constants.BEROUTE_SESSION_NOTE);
            if (beroute != null) {
                encoded.append(Constants.BEROUTE_URI_PARAMETER);
                encoded.append(beroute);
            }
            String bekey = (String) httpSession.getNote(
                Constants.BEKEY_SESSION_NOTE);
            if (bekey != null) {
                encoded.append(Constants.BEKEY_URI_PARAMETER);
                encoded.append(bekey);
            }
        }

        encoded.append(anchor);
        encoded.append(query);

        return encoded.toString();
    }

    /*
     * Checks to see if the request url of the request that was used to
     * create or resume the underlying HTTP session contains, in encoded
     * form, any parameter with the given name, and if so, returns its value.
     */
    private String parseRequestParameter(String search) {

        String requestUrlString = (String) httpSession.getNote(
            Constants.REQUEST_URL_SESSION_NOTE);
        if (requestUrlString == null) {
            return null;
        }

        String value = null;

        int index = requestUrlString.indexOf(search);
        if (index != -1) {
            int beginId = index + search.length();
            int semicolon = requestUrlString.indexOf(';', beginId);
            if (semicolon != -1) {
                value = requestUrlString.substring(beginId, semicolon);
            } else {
                value = requestUrlString.substring(beginId);
            }
        }

        return value;
    }
}
