/*
 * 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.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 LogUtil SIP_LOGGER = new LogUtil(LogUtil.SIP_LOG_DOMAIN);
    private static final String SIP_APP_SESSION_ID_PARAM = ";sipappsessionid=";
    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,
        SipSessionManager sipSessionManager) {
        super(httpSession);
        this.httpSession = httpSession;
        this.sipSessionManager = sipSessionManager;

        StandardContext ctxt = (StandardContext) httpSession.getManager()
                                                            .getContainer();
        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.
            // Notice that getRequestURL() returns the request URL *without*
            // any query string.
            StringBuffer sb = httpSession.getRequestURL();

            if (sb != null) {
                String requestURL = sb.toString();
                int index = requestURL.indexOf(SIP_APP_SESSION_ID_PARAM);

                if (index != -1) {
                    int beginId = index + SIP_APP_SESSION_ID_PARAM.length();
                    int semicolon = requestURL.indexOf(';', beginId);

                    if (semicolon != -1) {
                        sipAppSessionId = requestURL.substring(beginId,
                                semicolon);
                    } else {
                        sipAppSessionId = requestURL.substring(beginId);
                    }
                }
            }
        }

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

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

            sipAppSession = sipFactory.createApplicationSession();
        }

        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 (sipAppSession == null) {
            return url;
        } else {
            String sasId = sipAppSession.getId();

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

    /**
     * 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
        }

        StringBuffer reqUrlStr = httpSession.getRequestURL();

        if (reqUrlStr == null) {
            throw new IllegalStateException("No request URL"); // XXX LOC
        }

        URL requestUrl = null;

        try {
            requestUrl = new URL(reqUrlStr.toString());
        } 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
     * @param sasId The SAS id
     *
     * @return true if the given URL needs to be encoded, false otherwise
     */
    private boolean isEncodeable(String urlString, String sasId) {
        // 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;
            }

            // Does the given URL already have the SAS id encoded in it?
            if (file.indexOf(SIP_APP_SESSION_ID_PARAM + sasId) >= 0) {
                return false;
            }
        }

        return true;
    }

    /**
     * Encodes the given URL with the given SAS id.
     *
     * @param url The URL to be encoded
     * @param sasId The SAS id
     *
     * @return The encoded URL
     */
    private String toEncoded(String url, String sasId) {
        if ((url == null) || (sasId == 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.append(SIP_APP_SESSION_ID_PARAM);
            encoded.append(sasId);
        }

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

        return encoded.toString();
    }
}
