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