content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH & Co. KG (https://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 & Co. KG, please see the
   * company website: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://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.CmsObject;
  import org.opencms.file.CmsPropertyDefinition;
  import org.opencms.flex.CmsFlexController;
  import org.opencms.flex.CmsFlexRequest;
  import org.opencms.flex.CmsFlexResponse;
  import org.opencms.i18n.CmsAcceptLanguageHeaderParser;
  import org.opencms.i18n.CmsMessages;
  import org.opencms.jsp.CmsJspActionElement;
  import org.opencms.main.CmsLog;
  import org.opencms.main.OpenCms;
  import org.opencms.security.CmsRole;
  import org.opencms.site.CmsSite;
  import org.opencms.staticexport.CmsLinkManager;
  import org.opencms.util.CmsRequestUtil;
  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;
  
  import org.apache.commons.logging.Log;
  
  /**
   * 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>
   *
   * @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 log object for this class. */
      private static final Log LOG = CmsLog.getLog(CmsJspStatusBean.class);
  
      /** 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);
      }
  
      /**
       * Tries to forward to the configured error page for the current site root if present.<p>
       *
       * @param rootPath the resource to be used as error page
       *
       * @return <code>true</code> if the forward was successful performed, <code>false</code> otherwise
       *
       * @deprecated will not work for container pages and other pages using cms:include tags
       */
      @Deprecated
      public boolean forwardToErrorPage(String rootPath) {
  
          try {
  
              // get the site of the the error page resource
              CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(rootPath);
  
              // initialize a CMS object for the given resource
              CmsObject clone = OpenCms.initCmsObject(getCmsObject());
              if (site != null) {
                  clone.getRequestContext().setSiteRoot(site.getSiteRoot());
              }
              String relPath = clone.getRequestContext().removeSiteRoot(rootPath);
              clone.getRequestContext().setUri(relPath);
  
              // create a new flex controller together with its flex request/response
              // initialized with the context of the error page
              CmsFlexController ori = CmsFlexController.getController(getRequest());
              CmsFlexController controller = new CmsFlexController(
                  clone,
                  clone.readResource(relPath),
                  ori.getCmsCache(),
                  getRequest(),
                  getResponse(),
                  false,
                  false);
              // controller.setForwardMode(true);
              CmsFlexController.setController(getRequest(), controller);
              CmsFlexRequest f_req = new CmsFlexRequest(getRequest(), controller);
              CmsFlexResponse f_res = new CmsFlexResponse(getResponse(), controller, false, false);
              controller.push(f_req, f_res);
  
              // send the forward
              CmsRequestUtil.forwardRequest(
                  OpenCms.getStaticExportManager().getVfsPrefix() + relPath,
                  getRequest(),
                  getResponse());
  
          } catch (Throwable e) {
              // something went wrong log the exception and return false
              LOG.error(e.getMessage(), e);
              return false;
          }
          // return success flag
          return true;
      }
  
      /**
       * 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())) {
              return CmsLinkManager.removeOpenCmsContext(getRequestUri());
          }
          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<String, Object> 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);
      }
  
      /**
       * Writes the exception into the 'opencms.log', if the exception is not <code>null</code>.<p>
       */
      public void logException() {
  
          if (m_exception != null) {
              LOG.error(m_exception.getMessage(), m_exception);
          }
      }
  
      /**
       * 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 = Integer.valueOf(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<Locale> acceptedLocales = parser.getAcceptedLocales();
          List<Locale> 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;
      }
  }