001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp.util; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsPropertyDefinition; 032import org.opencms.flex.CmsFlexController; 033import org.opencms.flex.CmsFlexRequest; 034import org.opencms.flex.CmsFlexResponse; 035import org.opencms.i18n.CmsAcceptLanguageHeaderParser; 036import org.opencms.i18n.CmsMessages; 037import org.opencms.jsp.CmsJspActionElement; 038import org.opencms.main.CmsLog; 039import org.opencms.main.OpenCms; 040import org.opencms.security.CmsRole; 041import org.opencms.site.CmsSite; 042import org.opencms.staticexport.CmsLinkManager; 043import org.opencms.util.CmsRequestUtil; 044import org.opencms.util.CmsStringUtil; 045import org.opencms.workplace.CmsWorkplace; 046 047import java.util.Date; 048import java.util.List; 049import java.util.Locale; 050import java.util.Map; 051 052import org.apache.commons.logging.Log; 053 054import jakarta.servlet.RequestDispatcher; 055import jakarta.servlet.http.HttpServletRequest; 056import jakarta.servlet.http.HttpServletResponse; 057import jakarta.servlet.jsp.JspException; 058import jakarta.servlet.jsp.PageContext; 059 060/** 061 * This bean provides methods to generate customized http status error pages, e.g. to handle 404 (not found) errors.<p> 062 * 063 * The JSPs using this bean are placed in the OpenCms VFS folder /system/handler/.<p> 064 * 065 * @since 6.0 066 */ 067public class CmsJspStatusBean extends CmsJspActionElement { 068 069 /** Request attribute key for the error message. */ 070 public static final String ERROR_MESSAGE = RequestDispatcher.ERROR_MESSAGE; 071 072 /** Request attribute key for the error request URI. */ 073 public static final String ERROR_REQUEST_URI = RequestDispatcher.ERROR_REQUEST_URI; 074 075 /** Request attribute key for the error servlet name. */ 076 public static final String ERROR_SERVLET_NAME = RequestDispatcher.ERROR_SERVLET_NAME; 077 078 /** Request attribute key for the error status code. */ 079 public static final String ERROR_STATUS_CODE = RequestDispatcher.ERROR_STATUS_CODE; 080 081 /** Default name for an unknown error status code. */ 082 public static final String UNKKNOWN_STATUS_CODE = "unknown"; 083 084 /** The OpenCms VFS path containing the handler files. */ 085 public static final String VFS_FOLDER_HANDLER = CmsWorkplace.VFS_PATH_SYSTEM + "handler/"; 086 087 /** The log object for this class. */ 088 private static final Log LOG = CmsLog.getLog(CmsJspStatusBean.class); 089 090 /** The error message. */ 091 private String m_errorMessage; 092 093 /** The thrown exception. */ 094 private Throwable m_exception; 095 096 /** The Locale to use for displayed messages. */ 097 private Locale m_locale; 098 099 /** Contains all possible parameters usable by localized messages. */ 100 private Object[] m_localizeParameters; 101 102 /** The localized messages to use on the page. */ 103 private CmsMessages m_messages; 104 105 /** The request URI. */ 106 private String m_requestUri; 107 108 /** The servlet name. */ 109 private String m_servletName; 110 111 /** The site root of the requested resource. */ 112 private String m_siteRoot; 113 114 /** The status code. */ 115 private Integer m_statusCode; 116 117 /** The status code as message. */ 118 private String m_statusCodeMessage; 119 120 /** The URI used for template part inclusion. */ 121 private String m_templateUri; 122 123 /** 124 * Empty constructor, required for every JavaBean. 125 */ 126 public CmsJspStatusBean() { 127 128 super(); 129 } 130 131 /** 132 * Constructor, with parameters. 133 * 134 * @param context the JSP page context object 135 * @param req the JSP request 136 * @param res the JSP response 137 */ 138 public CmsJspStatusBean(PageContext context, HttpServletRequest req, HttpServletResponse res) { 139 140 super(context, req, res); 141 initMembers(req, null); 142 } 143 144 /** 145 * Constructor, with parameters. 146 * 147 * @param context the JSP page context object 148 * @param req the JSP request 149 * @param res the JSP response 150 * @param t the exception that lead to the error 151 */ 152 public CmsJspStatusBean(PageContext context, HttpServletRequest req, HttpServletResponse res, Throwable t) { 153 154 super(context, req, res); 155 initMembers(req, t); 156 } 157 158 /** 159 * Tries to forward to the configured error page for the current site root if present.<p> 160 * 161 * @param rootPath the resource to be used as error page 162 * 163 * @return <code>true</code> if the forward was successful performed, <code>false</code> otherwise 164 * 165 * @deprecated will not work for container pages and other pages using cms:include tags 166 */ 167 @Deprecated 168 public boolean forwardToErrorPage(String rootPath) { 169 170 try { 171 172 // get the site of the the error page resource 173 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(rootPath); 174 175 // initialize a CMS object for the given resource 176 CmsObject clone = OpenCms.initCmsObject(getCmsObject()); 177 if (site != null) { 178 clone.getRequestContext().setSiteRoot(site.getSiteRoot()); 179 } 180 String relPath = clone.getRequestContext().removeSiteRoot(rootPath); 181 clone.getRequestContext().setUri(relPath); 182 183 // create a new flex controller together with its flex request/response 184 // initialized with the context of the error page 185 CmsFlexController ori = CmsFlexController.getController(getRequest()); 186 CmsFlexController controller = new CmsFlexController( 187 clone, 188 clone.readResource(relPath), 189 ori.getCmsCache(), 190 getRequest(), 191 getResponse(), 192 false, 193 false); 194 // controller.setForwardMode(true); 195 CmsFlexController.setController(getRequest(), controller); 196 CmsFlexRequest f_req = new CmsFlexRequest(getRequest(), controller); 197 CmsFlexResponse f_res = new CmsFlexResponse(getResponse(), controller, false, false); 198 controller.push(f_req, f_res); 199 200 // send the forward 201 CmsRequestUtil.forwardRequest( 202 OpenCms.getStaticExportManager().getVfsPrefix() + relPath, 203 getRequest(), 204 getResponse()); 205 206 } catch (Throwable e) { 207 // something went wrong log the exception and return false 208 LOG.error(e.getMessage(), e); 209 return false; 210 } 211 // return success flag 212 return true; 213 } 214 215 /** 216 * Returns the error message.<p> 217 * 218 * @return the error message 219 */ 220 public String getErrorMessage() { 221 222 return m_errorMessage; 223 } 224 225 /** 226 * Returns the exception.<p> 227 * 228 * @return the exception 229 */ 230 public Throwable getException() { 231 232 return m_exception; 233 } 234 235 /** 236 * Returns the locale to use for the error page.<p> 237 * 238 * @return the locale to use for the error page 239 */ 240 public Locale getLocale() { 241 242 return m_locale; 243 } 244 245 /** 246 * Returns the processed output of the specified element of an OpenCms page.<p> 247 * 248 * The page to get the content from is looked up in the property value "template-elements". 249 * If no value is found, the page is read from the "contents/" subfolder of the handler folder.<p> 250 * 251 * For each status code, an individual page can be created by naming it "content${STATUSCODE}.html". 252 * If the individual page can not be found, the content is read from "contentunknown.html".<p> 253 * 254 * @param element name of the element 255 * @return the processed output of the specified element of an OpenCms page 256 */ 257 public String getPageContent(String element) { 258 259 // Determine the folder to read the contents from 260 String contentFolder = property(CmsPropertyDefinition.PROPERTY_TEMPLATE_ELEMENTS, "search", ""); 261 if (CmsStringUtil.isEmpty(contentFolder)) { 262 contentFolder = VFS_FOLDER_HANDLER + "contents/"; 263 } 264 265 // determine the file to read the contents from 266 String fileName = "content" + getStatusCodeMessage() + ".html"; 267 if (!getCmsObject().existsResource(contentFolder + fileName)) { 268 // special file does not exist, use generic one 269 fileName = "content" + UNKKNOWN_STATUS_CODE + ".html"; 270 } 271 272 // get the content 273 return getContent(contentFolder + fileName, element, getLocale()); 274 } 275 276 /** 277 * Returns the absolute path of the requested resource in the VFS of OpenCms.<p> 278 * 279 * @return the absolute path of the requested resource in the VFS of OpenCms 280 */ 281 public String getRequestResourceName() { 282 283 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getRequestUri())) { 284 return CmsLinkManager.removeOpenCmsContext(getRequestUri()); 285 } 286 return getRequestUri(); 287 } 288 289 /** 290 * Returns the request Uri.<p> 291 * 292 * @return the request Uri 293 */ 294 public String getRequestUri() { 295 296 return m_requestUri; 297 } 298 299 /** 300 * Returns the full Workplace resource path to the selected resource.<p> 301 * 302 * @param resourceName the name of the resource to get the resource path for 303 * 304 * @return the full Workplace resource path to the selected resource 305 */ 306 public String getResourceUri(String resourceName) { 307 308 return CmsWorkplace.getResourceUri(resourceName); 309 } 310 311 /** 312 * Returns the servlet name.<p> 313 * 314 * @return the servlet name 315 */ 316 public String getServletName() { 317 318 return m_servletName; 319 } 320 321 /** 322 * Returns the site root of the requested resource.<p> 323 * 324 * @return the site root of the requested resource 325 */ 326 public String getSiteRoot() { 327 328 return m_siteRoot; 329 } 330 331 /** 332 * Returns the status code.<p> 333 * 334 * @return the status code 335 */ 336 public Integer getStatusCode() { 337 338 return m_statusCode; 339 } 340 341 /** 342 * Returns the status code message.<p> 343 * 344 * @return the status code message 345 */ 346 public String getStatusCodeMessage() { 347 348 return m_statusCodeMessage; 349 } 350 351 /** 352 * Returns the URI used for template part inclusion.<p> 353 * 354 * @return the URI used for template part inclusion 355 */ 356 public String getTemplateUri() { 357 358 if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_templateUri)) { 359 m_templateUri = "/"; 360 } 361 return m_templateUri; 362 } 363 364 /** 365 * Include a template part to display on the error page.<p> 366 * 367 * @param target the target uri of the file in the OpenCms VFS (can be relative or absolute) 368 * @param element the element (template selector) to display from the target 369 * 370 * @throws JspException in case there were problems including the target 371 */ 372 public void includeTemplatePart(String target, String element) throws JspException { 373 374 includeTemplatePart(target, element, null); 375 } 376 377 /** 378 * Include a template part to display on the error page.<p> 379 * 380 * @param target the target uri of the file in the OpenCms VFS (can be relative or absolute) 381 * @param element the element (template selector) to display from the target 382 * @param parameterMap a map of the request parameters 383 * 384 * @throws JspException in case there were problems including the target 385 */ 386 public void includeTemplatePart(String target, String element, Map<String, Object> parameterMap) 387 throws JspException { 388 389 // store current site root and URI 390 String currentSiteRoot = getRequestContext().getSiteRoot(); 391 String currentUri = getRequestContext().getUri(); 392 393 try { 394 // set site root and URI to display template part correct 395 getRequestContext().setSiteRoot(getSiteRoot()); 396 getRequestContext().setUri(getTemplateUri()); 397 // include the template part 398 include(target, element, parameterMap); 399 } finally { 400 // reset site root and requested URI to status JSP 401 getRequestContext().setSiteRoot(currentSiteRoot); 402 getRequestContext().setUri(currentUri); 403 } 404 } 405 406 /** 407 * Returns the localized resource string for a given message key.<p> 408 * 409 * If the key was not found in the bundle, the return value is 410 * <code>"??? " + keyName + " ???"</code>.<p> 411 * 412 * The key can use the following parameters for formatting: 413 * <ul> 414 * <li>0: the HTTP status code</li> 415 * <li>1: the requested URI</li> 416 * <li>2: the generated error message</li> 417 * <li>3: the servlet name</li> 418 * <li>4: the date of the request</li> 419 * </ul> 420 * 421 * @param keyName the key for the desired string 422 * @return the resource string for the given key 423 */ 424 public String key(String keyName) { 425 426 return key(keyName, null); 427 } 428 429 /** 430 * Returns the localized resource string for a given message key.<p> 431 * 432 * For a detailed parameter description, see {@link CmsJspStatusBean#key(String)}.<p> 433 * 434 * @param keyName the key for the desired string 435 * @param defaultKeyName the default key for the desired string, used if the keyName delivered no resource string 436 * @return the resource string for the given key 437 */ 438 public String key(String keyName, String defaultKeyName) { 439 440 String value = getMessages().key(keyName, getLocalizeParameters()); 441 if (value.startsWith(CmsMessages.UNKNOWN_KEY_EXTENSION) && CmsStringUtil.isNotEmpty(defaultKeyName)) { 442 value = getMessages().key(defaultKeyName, getLocalizeParameters()); 443 } 444 return CmsStringUtil.escapeHtml(value); 445 } 446 447 /** 448 * Returns the localized resource string for a given message key depending on the HTTP status.<p> 449 * 450 * Automatically adds a status suffix for the key to get, eg. "keyname" gets the suffix "_404" for a 404 status. 451 * For a detailed parameter description, see {@link CmsJspStatusBean#key(String)}.<p> 452 * 453 * @param keyName the key for the desired string 454 * @return the resource string for the given key 455 */ 456 public String keyStatus(String keyName) { 457 458 keyName += "_"; 459 return key(keyName + getStatusCodeMessage(), keyName + UNKKNOWN_STATUS_CODE); 460 } 461 462 /** 463 * Writes the exception into the 'opencms.log', if the exception is not <code>null</code>.<p> 464 */ 465 public void logException() { 466 467 if (m_exception != null) { 468 LOG.error(m_exception.getMessage(), m_exception); 469 } 470 } 471 472 /** 473 * Sets the URI used for template part inclusion.<p> 474 * 475 * @param templateUri the URI used for template part inclusion 476 */ 477 public void setTemplateUri(String templateUri) { 478 479 m_templateUri = templateUri; 480 } 481 482 /** 483 * Returns true if the current user has the "DEVELOPER" role and can view the exception stacktrace.<p> 484 * 485 * @return true if the current user has the "DEVELOPER" role and can view the exception stacktrace 486 */ 487 public boolean showException() { 488 489 return OpenCms.getRoleManager().hasRole(getCmsObject(), CmsRole.DEVELOPER); 490 } 491 492 /** 493 * Returns the parameter object for localization.<p> 494 * 495 * @return the parameter object for localization 496 * 497 * @see #key(String) for a more detailed object description 498 */ 499 protected Object[] getLocalizeParameters() { 500 501 if (m_localizeParameters == null) { 502 m_localizeParameters = new Object[] { 503 getStatusCodeMessage(), 504 getRequestUri(), 505 getErrorMessage(), 506 getServletName(), 507 new Date(getRequestContext().getRequestTime())}; 508 } 509 return m_localizeParameters; 510 } 511 512 /** 513 * Returns the initialized messages object to read localized messages from.<p> 514 * 515 * @return the initialized messages object to read localized messages from 516 */ 517 protected CmsMessages getMessages() { 518 519 if (m_messages == null) { 520 // initialize the localized messages 521 m_messages = new CmsMessages(Messages.get().getBundleName(), getLocale().toString()); 522 } 523 return m_messages; 524 } 525 526 /** 527 * Initializes the members of this bean with the information retrieved from the current request.<p> 528 * 529 * @param req the JSP request 530 * @param t the exception that lead to the error 531 */ 532 protected void initMembers(HttpServletRequest req, Throwable t) { 533 534 // get the status error attribute values from the request 535 m_servletName = (String)req.getAttribute(ERROR_SERVLET_NAME); 536 m_errorMessage = (String)req.getAttribute(ERROR_MESSAGE); 537 m_requestUri = (String)req.getAttribute(ERROR_REQUEST_URI); 538 m_statusCode = (Integer)req.getAttribute(ERROR_STATUS_CODE); 539 540 if ((m_statusCode == null) || (m_requestUri == null)) { 541 // check if the error request is invoked via Apache/HTTPd ErrorDocument and mod_jk 542 543 // to use this you need to add the following to "jk.conf": 544 545 // JkEnvVar REDIRECT_URL none 546 // JkEnvVar REDIRECT_STATUS none 547 // JkEnvVar REDIRECT_SERVLET_NAME OpenCmsServlet 548 549 String jkUri = (String)req.getAttribute("REDIRECT_URL"); 550 String jkStatusCode = (String)req.getAttribute("REDIRECT_STATUS"); 551 String jkServletName = (String)req.getAttribute("REDIRECT_SERVLET_NAME"); 552 try { 553 if (!"none".equals(jkStatusCode) && !"none".equals(jkUri)) { 554 m_servletName = jkServletName; 555 m_requestUri = jkUri; 556 m_statusCode = Integer.valueOf(jkStatusCode); 557 } 558 } catch (NullPointerException e) { 559 // attibute not set, ignore 560 } catch (NumberFormatException e) { 561 // status code not a number, ignore 562 } 563 } 564 565 // get the status code as String 566 if (m_statusCode != null) { 567 m_statusCodeMessage = String.valueOf(m_statusCode.intValue()); 568 } else { 569 m_statusCodeMessage = UNKKNOWN_STATUS_CODE; 570 } 571 572 m_exception = t; 573 574 // determine the best locale to use from the users browser settings 575 CmsAcceptLanguageHeaderParser parser = new CmsAcceptLanguageHeaderParser( 576 req, 577 OpenCms.getWorkplaceManager().getDefaultLocale()); 578 List<Locale> acceptedLocales = parser.getAcceptedLocales(); 579 List<Locale> workplaceLocales = OpenCms.getWorkplaceManager().getLocales(); 580 m_locale = OpenCms.getLocaleManager().getFirstMatchingLocale(acceptedLocales, workplaceLocales); 581 if (m_locale == null) { 582 // no match found - use OpenCms default locale 583 m_locale = OpenCms.getWorkplaceManager().getDefaultLocale(); 584 } 585 getCmsObject().getRequestContext().setLocale(m_locale); 586 587 // store the site root of the request 588 m_siteRoot = OpenCms.getSiteManager().matchRequest(req).getSiteRoot(); 589 } 590 591 /** 592 * Sets the error message.<p> 593 * 594 * @param errorMessage the error message to set 595 */ 596 protected void setErrorMessage(String errorMessage) { 597 598 m_errorMessage = errorMessage; 599 } 600 601 /** 602 * Sets the exception.<p> 603 * 604 * @param exception the exception to set 605 */ 606 protected void setException(Throwable exception) { 607 608 m_exception = exception; 609 } 610 611 /** 612 * Sets the locale to use for the error page.<p> 613 * 614 * @param locale the locale to use for the error page 615 */ 616 protected void setLocale(Locale locale) { 617 618 m_locale = locale; 619 getCmsObject().getRequestContext().setLocale(m_locale); 620 } 621 622 /** 623 * Sets the parameter object for localization.<p> 624 * 625 * @param localizeParameters the parameter object for localization 626 */ 627 protected void setLocalizeParameters(Object[] localizeParameters) { 628 629 m_localizeParameters = localizeParameters; 630 } 631 632 /** 633 * Sets the initialized messages object to read localized messages from.<p> 634 * 635 * @param messages the initialized messages object to read localized messages from 636 */ 637 protected void setMessages(CmsMessages messages) { 638 639 m_messages = messages; 640 } 641 642 /** 643 * Sets the request Uri.<p> 644 * 645 * @param requestUri the request Uri to set 646 */ 647 protected void setRequestUri(String requestUri) { 648 649 m_requestUri = requestUri; 650 } 651 652 /** 653 * Sets the servlet name.<p> 654 * 655 * @param servletName the servlet name to set 656 */ 657 protected void setServletName(String servletName) { 658 659 m_servletName = servletName; 660 } 661 662 /** 663 * Sets the site root of the requested resource.<p> 664 * 665 * @param siteRoot the site root of the requested resource 666 */ 667 protected void setSiteRoot(String siteRoot) { 668 669 m_siteRoot = siteRoot; 670 } 671 672 /** 673 * Sets the status code.<p> 674 * 675 * @param statusCode the status code to set 676 */ 677 protected void setStatusCode(Integer statusCode) { 678 679 m_statusCode = statusCode; 680 } 681 682 /** 683 * Sets the status code message.<p> 684 * 685 * @param statusCodeMessage the status code message to set 686 */ 687 protected void setStatusCodeMessage(String statusCodeMessage) { 688 689 m_statusCodeMessage = statusCodeMessage; 690 } 691}