/*
 * File   : $Source: /usr/local/cvs/opencms/src/org/opencms/jsp/util/CmsJspStatusBean.java,v $
 * Date   : $Date: 2008-02-27 12:05:49 $
 * Version: $Revision: 1.14 $
 *
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.jsp.util;

import org.opencms.file.CmsPropertyDefinition;
import org.opencms.i18n.CmsAcceptLanguageHeaderParser;
import org.opencms.i18n.CmsMessages;
import org.opencms.jsp.CmsJspActionElement;
import org.opencms.main.OpenCms;
import org.opencms.security.CmsRole;
import org.opencms.util.CmsStringUtil;
import org.opencms.workplace.CmsWorkplace;

import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;

/**
 * This bean provides methods to generate customized http status error pages, e.g. to handle 404 (not found) errors.<p>
 * 
 * The JSPs using this bean are placed in the OpenCms VFS folder /system/handler/.<p>
 * 
 * @author Andreas Zahner 
 * 
 * @version $Revision: 1.14 $
 * 
 * @since 6.0
 */
public class CmsJspStatusBean extends CmsJspActionElement {

    /** Request attribute key for the error message. */
    public static final String ERROR_MESSAGE = "javax.servlet.error.message";

    /** Request attribute key for the error request URI. */
    public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";

    /** Request attribute key for the error servlet name. */
    public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";

    /** Request attribute key for the error status code. */
    public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";

    /** Default name for an unknown error status code. */
    public static final String UNKKNOWN_STATUS_CODE = "unknown";

    /** The OpenCms VFS path containing the handler files. */
    public static final String VFS_FOLDER_HANDLER = CmsWorkplace.VFS_PATH_SYSTEM + "handler/";

    /** The error message. */
    private String m_errorMessage;

    /** The thrown exception. */
    private Throwable m_exception;

    /** The Locale to use for displayed messages. */
    private Locale m_locale;

    /** Contains all possible parameters usable by localized messages. */
    private Object[] m_localizeParameters;

    /** The localized messages to use on the page. */
    private CmsMessages m_messages;

    /** The request URI. */
    private String m_requestUri;

    /** The servlet name. */
    private String m_servletName;

    /** The site root of the requested resource. */
    private String m_siteRoot;

    /** The status code. */
    private Integer m_statusCode;

    /** The status code as message. */
    private String m_statusCodeMessage;

    /** The URI used for template part inclusion. */
    private String m_templateUri;

    /**
     * Empty constructor, required for every JavaBean.
     */
    public CmsJspStatusBean() {

        super();
    }

    /**
     * Constructor, with parameters.
     * 
     * @param context the JSP page context object
     * @param req the JSP request 
     * @param res the JSP response 
     */
    public CmsJspStatusBean(PageContext context, HttpServletRequest req, HttpServletResponse res) {

        super(context, req, res);
        initMembers(req, null);
    }

    /**
     * Constructor, with parameters.
     * 
     * @param context the JSP page context object
     * @param req the JSP request 
     * @param res the JSP response 
     * @param t the exception that lead to the error
     */
    public CmsJspStatusBean(PageContext context, HttpServletRequest req, HttpServletResponse res, Throwable t) {

        super(context, req, res);
        initMembers(req, t);
    }

    /**
     * Returns the error message.<p>
     *
     * @return the error message
     */
    public String getErrorMessage() {

        return m_errorMessage;
    }

    /**
     * Returns the exception.<p>
     *
     * @return the exception
     */
    public Throwable getException() {

        return m_exception;
    }

    /**
     * Returns the locale to use for the error page.<p>
     *
     * @return the locale to use for the error page
     */
    public Locale getLocale() {

        return m_locale;
    }

    /**
     * Returns the processed output of the specified element of an OpenCms page.<p>
     * 
     * The page to get the content from is looked up in the property value "template-elements".
     * If no value is found, the page is read from the "contents/" subfolder of the handler folder.<p>
     * 
     * For each status code, an individual page can be created by naming it "content${STATUSCODE}.html". 
     * If the individual page can not be found, the content is read from "contentunknown.html".<p>
     * 
     * @param element name of the element
     * @return the processed output of the specified element of an OpenCms page
     */
    public String getPageContent(String element) {

        // Determine the folder to read the contents from
        String contentFolder = property(CmsPropertyDefinition.PROPERTY_TEMPLATE_ELEMENTS, "search", "");
        if (CmsStringUtil.isEmpty(contentFolder)) {
            contentFolder = VFS_FOLDER_HANDLER + "contents/";
        }

        // determine the file to read the contents from
        String fileName = "content" + getStatusCodeMessage() + ".html";
        if (!getCmsObject().existsResource(contentFolder + fileName)) {
            // special file does not exist, use generic one
            fileName = "content" + UNKKNOWN_STATUS_CODE + ".html";
        }

        // get the content
        return getContent(contentFolder + fileName, element, getLocale());
    }

    /**
     * Returns the absolute path of the requested resource in the VFS of OpenCms.<p>
     *  
     * @return the absolute path of the requested resource in the VFS of OpenCms
     */
    public String getRequestResourceName() {

        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getRequestUri())
            && getRequestUri().startsWith(OpenCms.getSystemInfo().getOpenCmsContext())) {
            return getRequestUri().substring(OpenCms.getSystemInfo().getOpenCmsContext().length());
        }
        return getRequestUri();
    }

    /**
     * Returns the request Uri.<p>
     *
     * @return the request Uri
     */
    public String getRequestUri() {

        return m_requestUri;
    }

    /**
     * Returns the full Workplace resource path to the selected resource.<p>
     * 
     * @param resourceName the name of the resource to get the resource path for
     * 
     * @return the full Workplace resource path to the selected resource
     */
    public String getResourceUri(String resourceName) {

        return CmsWorkplace.getResourceUri(resourceName);
    }

    /**
     * Returns the servlet name.<p>
     *
     * @return the servlet name
     */
    public String getServletName() {

        return m_servletName;
    }

    /**
     * Returns the site root of the requested resource.<p>
     *
     * @return the site root of the requested resource
     */
    public String getSiteRoot() {

        return m_siteRoot;
    }

    /**
     * Returns the status code.<p>
     *
     * @return the status code
     */
    public Integer getStatusCode() {

        return m_statusCode;
    }

    /**
     * Returns the status code message.<p>
     *
     * @return the status code message
     */
    public String getStatusCodeMessage() {

        return m_statusCodeMessage;
    }

    /**
     * Returns the URI used for template part inclusion.<p>
     *
     * @return the URI used for template part inclusion
     */
    public String getTemplateUri() {

        if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_templateUri)) {
            m_templateUri = "/";
        }
        return m_templateUri;
    }

    /**
     * Include a template part to display on the error page.<p>
     * 
     * @param target the target uri of the file in the OpenCms VFS (can be relative or absolute)
     * @param element the element (template selector) to display from the target
     * 
     * @throws JspException in case there were problems including the target
     */
    public void includeTemplatePart(String target, String element) throws JspException {

        includeTemplatePart(target, element, null);
    }

    /**
     * Include a template part to display on the error page.<p>
     * 
     * @param target the target uri of the file in the OpenCms VFS (can be relative or absolute)
     * @param element the element (template selector) to display from the target
     * @param parameterMap a map of the request parameters
     * 
     * @throws JspException in case there were problems including the target
     */
    public void includeTemplatePart(String target, String element, Map parameterMap) throws JspException {

        // store current site root and URI
        String currentSiteRoot = getRequestContext().getSiteRoot();
        String currentUri = getRequestContext().getUri();

        try {
            // set site root and URI to display template part correct
            getRequestContext().setSiteRoot(getSiteRoot());
            getRequestContext().setUri(getTemplateUri());
            // include the template part
            include(target, element, parameterMap);
        } finally {
            // reset site root and requested URI to status JSP
            getRequestContext().setSiteRoot(currentSiteRoot);
            getRequestContext().setUri(currentUri);
        }
    }

    /**
     * Returns the localized resource string for a given message key.<p>
     * 
     * If the key was not found in the bundle, the return value is
     * <code>"??? " + keyName + " ???"</code>.<p>
     * 
     * The key can use the following parameters for formatting:
     * <ul>
     * <li>0: the HTTP status code</li>
     * <li>1: the requested URI</li>
     * <li>2: the generated error message</li>
     * <li>3: the servlet name</li>
     * <li>4: the date of the request</li>
     * </ul>
     * 
     * @param keyName the key for the desired string 
     * @return the resource string for the given key 
     */
    public String key(String keyName) {

        return key(keyName, null);
    }

    /**
     * Returns the localized resource string for a given message key.<p>
     * 
     * For a detailed parameter description, see {@link CmsJspStatusBean#key(String)}.<p>
     * 
     * @param keyName the key for the desired string
     * @param defaultKeyName the default key for the desired string, used if the keyName delivered no resource string
     * @return the resource string for the given key 
     */
    public String key(String keyName, String defaultKeyName) {

        String value = getMessages().key(keyName, getLocalizeParameters());
        if (value.startsWith(CmsMessages.UNKNOWN_KEY_EXTENSION) && CmsStringUtil.isNotEmpty(defaultKeyName)) {
            value = getMessages().key(defaultKeyName, getLocalizeParameters());
        }
        return CmsStringUtil.escapeHtml(value);
    }

    /**
     * Returns the localized resource string for a given message key depending on the HTTP status.<p>
     * 
     * Automatically adds a status suffix for the key to get, eg. "keyname" gets the suffix "_404" for a 404 status.
     * For a detailed parameter description, see {@link CmsJspStatusBean#key(String)}.<p>
     * 
     * @param keyName the key for the desired string 
     * @return the resource string for the given key 
     */
    public String keyStatus(String keyName) {

        keyName += "_";
        return key(keyName + getStatusCodeMessage(), keyName + UNKKNOWN_STATUS_CODE);
    }

    /**
     * Sets the URI used for template part inclusion.<p>
     *
     * @param templateUri the URI used for template part inclusion
     */
    public void setTemplateUri(String templateUri) {

        m_templateUri = templateUri;
    }

    /**
     * Returns true if the current user has the "DEVELOPER" role and can view the exception stacktrace.<p>
     * 
     * @return true if the current user has the "DEVELOPER" role and can view the exception stacktrace
     */
    public boolean showException() {

        return OpenCms.getRoleManager().hasRole(getCmsObject(), CmsRole.DEVELOPER);
    }

    /**
     * Returns the parameter object for localization.<p>
     * 
     * @return the parameter object for localization
     * 
     * @see #key(String) for a more detailed object description
     */
    protected Object[] getLocalizeParameters() {

        if (m_localizeParameters == null) {
            m_localizeParameters = new Object[] {
                getStatusCodeMessage(),
                getRequestUri(),
                getErrorMessage(),
                getServletName(),
                new Date(getRequestContext().getRequestTime())};
        }
        return m_localizeParameters;
    }

    /**
     * Returns the initialized messages object to read localized messages from.<p>
     * 
     * @return the initialized messages object to read localized messages from
     */
    protected CmsMessages getMessages() {

        if (m_messages == null) {
            // initialize the localized messages
            m_messages = new CmsMessages(Messages.get().getBundleName(), getLocale().toString());
        }
        return m_messages;
    }

    /**
     * Initializes the members of this bean with the information retrieved from the current request.<p>
     * 
     * @param req the JSP request 
     * @param t the exception that lead to the error
     */
    protected void initMembers(HttpServletRequest req, Throwable t) {

        // get the status error attribute values from the request
        m_servletName = (String)req.getAttribute(ERROR_SERVLET_NAME);
        m_errorMessage = (String)req.getAttribute(ERROR_MESSAGE);
        m_requestUri = (String)req.getAttribute(ERROR_REQUEST_URI);
        m_statusCode = (Integer)req.getAttribute(ERROR_STATUS_CODE);

        if ((m_statusCode == null) || (m_requestUri == null)) {
            // check if the error request is invoked via Apache/HTTPd ErrorDocument and mod_jk

            // to use this you need to add the following to "jk.conf":

            // JkEnvVar REDIRECT_URL none
            // JkEnvVar REDIRECT_STATUS none
            // JkEnvVar REDIRECT_SERVLET_NAME OpenCmsServlet         

            String jkUri = (String)req.getAttribute("REDIRECT_URL");
            String jkStatusCode = (String)req.getAttribute("REDIRECT_STATUS");
            String jkServletName = (String)req.getAttribute("REDIRECT_SERVLET_NAME");
            try {
                if (!"none".equals(jkStatusCode) && !"none".equals(jkUri)) {
                    m_servletName = jkServletName;
                    m_requestUri = jkUri;
                    m_statusCode = new Integer(jkStatusCode);
                }
            } catch (NullPointerException e) {
                // attibute not set, ignore
            } catch (NumberFormatException e) {
                // status code not a number, ignore
            }
        }

        // get the status code as String
        if (m_statusCode != null) {
            m_statusCodeMessage = String.valueOf(m_statusCode.intValue());
        } else {
            m_statusCodeMessage = UNKKNOWN_STATUS_CODE;
        }

        m_exception = t;

        // determine the best locale to use from the users browser settings
        CmsAcceptLanguageHeaderParser parser = new CmsAcceptLanguageHeaderParser(
            req,
            OpenCms.getWorkplaceManager().getDefaultLocale());
        List acceptedLocales = parser.getAcceptedLocales();
        List workplaceLocales = OpenCms.getWorkplaceManager().getLocales();
        m_locale = OpenCms.getLocaleManager().getFirstMatchingLocale(acceptedLocales, workplaceLocales);
        if (m_locale == null) {
            // no match found - use OpenCms default locale
            m_locale = OpenCms.getWorkplaceManager().getDefaultLocale();
        }
        getCmsObject().getRequestContext().setLocale(m_locale);

        // store the site root of the request
        m_siteRoot = OpenCms.getSiteManager().matchRequest(req).getSiteRoot();
    }

    /**
     * Sets the error message.<p>
     *
     * @param errorMessage the error message to set
     */
    protected void setErrorMessage(String errorMessage) {

        m_errorMessage = errorMessage;
    }

    /**
     * Sets the exception.<p>
     *
     * @param exception the exception to set
     */
    protected void setException(Throwable exception) {

        m_exception = exception;
    }

    /**
     * Sets the locale to use for the error page.<p>
     *
     * @param locale the locale to use for the error page
     */
    protected void setLocale(Locale locale) {

        m_locale = locale;
        getCmsObject().getRequestContext().setLocale(m_locale);
    }

    /**
     * Sets the parameter object for localization.<p>
     * 
     * @param localizeParameters the parameter object for localization
     */
    protected void setLocalizeParameters(Object[] localizeParameters) {

        m_localizeParameters = localizeParameters;
    }

    /**
     * Sets the initialized messages object to read localized messages from.<p>
     * 
     * @param messages the initialized messages object to read localized messages from
     */
    protected void setMessages(CmsMessages messages) {

        m_messages = messages;
    }

    /**
     * Sets the request Uri.<p>
     *
     * @param requestUri the request Uri to set
     */
    protected void setRequestUri(String requestUri) {

        m_requestUri = requestUri;
    }

    /**
     * Sets the servlet name.<p>
     *
     * @param servletName the servlet name to set
     */
    protected void setServletName(String servletName) {

        m_servletName = servletName;
    }

    /**
     * Sets the site root of the requested resource.<p>
     *
     * @param siteRoot the site root of the requested resource
     */
    protected void setSiteRoot(String siteRoot) {

        m_siteRoot = siteRoot;
    }

    /**
     * Sets the status code.<p>
     *
     * @param statusCode the status code to set
     */
    protected void setStatusCode(Integer statusCode) {

        m_statusCode = statusCode;
    }

    /**
     * Sets the status code message.<p>
     *
     * @param statusCodeMessage the status code message to set
     */
    protected void setStatusCodeMessage(String statusCodeMessage) {

        m_statusCodeMessage = statusCodeMessage;
    }
}