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}