4. Advanced exception handling
Useful for fine grained exception handling control
When you need to have full control over all exception, and act differently upon different combinations of exception, this page is the way to go
First of all, for easy exception handling and error page creation, you can use one of the options described at Simple exception handling. But, as some are already indicated on that page to be not always usable, there is a general shortcoming for the useful options from that page:
Shortcoming of the simple exception handling
You do not have control over the entire component tree: the last HstComponent calling setStatus is honoured, and forward and sendRedirect are short-circuiting the processing: Other HstComponents are not executed anymore
It makes sense, that you do not want a HstComponent in its doBeforeRender method have the responsibility of handling errors. It is more powerful to assemble all HstComponentExceptions of all HstComponents that are executed, and then have one, customizable, class handle the exceptions the way you would like.
Runtime exception are handled different in development.mode = true or false
When running the site in developement.mode=true, runtime exceptions in HstComponents then are directly thrown. When developement.mode=false, they are being wrapped in a HstComponentException and can handled as described below
So, instead of calling forward or sendRedirect in your HstComponent, you create your own exceptions and throw these. Your exception can be a runtime exception or should extend from HstComponentException, for example:
MyDocumentNotFoundException:
public class MyDocumentNotFoundException extends HstComponentException { private static final long serialVersionUID = 1L; public MyDocumentNotFoundException() { super(); } public MyDocumentNotFoundException(String msg, Throwable nested) { super(msg, nested); } public MyDocumentNotFoundException(String message) { super(message); } public MyDocumentNotFoundException(Throwable nested) { super(nested); } }
Now, when in some HstComponent some bean cannot be found, you can invoke
throw new MyDocumentNotFoundException("Did not find a document at " + path);
Now, HstRequestProcessing is continued. Perhaps some other HstComponents also throw some exception extending from HstComponentException, or just throw an exception that is wrapped in a HstComponentException, for example:
try { HstQuery q = this.queryManager.createQuery(n); q.execute(); } catch (QueryException e) { throw new HstComponentException("Query exception", e); }
So, at the end of executing the doBeforeRender(...) (and similar for doAction but that is always a single component) of every HstComponent, there might have been a couple of HstComponents that threw an exception.
The PageErrorHandler
You can now implement a PageErrorHandler yourself, which based on some set of exceptions have some behaviour.
Implementing the PageErrorHandler, you need to implement one method, namely
Status handleComponentExceptions(PageErrors pageErrors, HstRequest hstRequest, HstResponse hstResponse);
where Status is an enum
enum Status { HANDLED_TO_STOP, NOT_HANDLED, HANDLED_BUT_CONTINUE, }
See at the end of this page an example of how your custom PageErrorHandler might look like
The HST ships with a DefaultPageErrorHandler (which you should not extend as it is part of the core), which logs all exceptions at a warning level. When you want to have your own PageErrorHandler to nicely control all kinds of exceptions, you can use the following ways to achieve this (see below for an indept explanation per option):
-
Override the DefaultPageErrorHandler by overriding the Spring configuration
-
Put the custom PageErrorHandler implementation as an attribute in the hstRequest. The attribute name must be ContainerConstants.CUSTOM_ERROR_HANDLER_PARAM_NAME.
-
Configure on the root HstComponent a parametername 'org.hippoecm.hst.core.container.custom.errorhandler' and a parameter value containing the fully qualified name of the PageErrorHandler implementation
1 Override the DefaultPageErrorHandler by overriding the Spring configuration
By default, hst container has a default page error handler, this can be overriden by definining custom page error handler in an overrides/ assembly xml file. For example, you can put an xml assembly in /META-INF/hst-assembly/overrides like the following example:
<bean id="org.hippoecm.hst.core.container.PageErrorHandler" class="org.hippoecm.hst.core.container.DefaultPageErrorHandler"> </bean>
By overriding the bean with the same ID, you can replace the default page error handler. In this case, you are replacing the default page error handler which covers all errors globally.
In order for this to work, ensure that your hst-config.properties file contains:
assembly.overrides = META-INF/hst-assembly/overrides/*.xml
2 Put the custom PageErrorHandler implementation as an attribute in the hstRequest
During execution of any HstComponent, you can set your custom PageErrorHandler on the hstRequest. When during execution of all the HstComponent exceptions were thrown, the HstRequestProcessing will invoke the PageErrorHandler. When there is one on the hstRequest, it will invoke this PageErrorHandler instead of the global one. Note that we do not advice runtime setting of the PageErrorHandler, but it is possible.
3 Configure the PageErrorHandler on the root HST Component configuration
You can have a different page error handler per root hst component.
For example, you could have the following hst:component configuration:
/detailpage: jcr:primaryType: hst:component hst:referencecomponent: hst:pages/standard hst:parameternames: [org.hippoecm.hst.core.container.custom.errorhandler, notfound, unauthorized] hst:parametervalues: [org.hippoecm.hst.demo.util.SimplePageErrorHandler, /error/404, /error/401]
Note From HST 2.10.01 and higher, you can better use the property hst:page_errorhandlerclassname instead of using the hst:parameternames and hst:parametervalues. The above configuration still works, but below is the nicer configuration:
From HST 2.10.01 and higher:
/detailpage: jcr:primaryType: hst:component hst:referencecomponent: hst:pages/standard hst:page_errorhandlerclassname: org.hippoecm.hst.demo.util.SimplePageErrorHandler hst:parameternames: [notfound, unauthorized] hst:parametervalues: [/error/404, /error/401]
The parametervalues are meant to be used for the forwarding or redirecting. Also, a sitemap should have the error/* matcher, as described at Simple exception handling.
Putting it al together in your CustomPageErrorHandler with custom exceptions.
Note that the actual exception checking with the if statements is not the nicest way to do it, but now for explanatory goals it is enlightening enough:
Tip Any exceptions thrown during the doBeforeRender() / doAction() phases are wrapped with a HstComponentException by the HST before passing it on to any error handler. In that case, the stacktrace of the HstComponentException itself does not provide any useful information, but the getCause() of the HstComponentException is the real stacktrace / message you're interested in. To prevent excessive logging, you can choose to log only the cause of any HstComponentException. Beware though that some HstComponentExceptions are thrown by the HST itself. Check for a non-null cause exception in that case.
Example of CustomPageErrorHandler with custom exceptions:
public class CustomPageErrorHandler implements PageErrorHandler { protected final static Logger log = LoggerFactory.getLogger( CustomPageErrorHandler.class); public Status handleComponentExceptions(PageErrors pageErrors, HstRequest hstRequest, HstResponse hstResponse) { logWarningsForEachComponentExceptions(pageErrors); ResolvedSiteMapItem resolvedSiteMapItem = hstRequest.getRequestContext().getResolvedSiteMapItem(); for (HstComponentInfo componentInfo : pageErrors.getComponentInfos()) { for (HstComponentException componentException : pageErrors.getComponentExceptions(componentInfo)) { String errorPage = null; if(componentException instanceof MyUnauthorizedException || componentException.getCause() instanceof MyUnauthorizedException) { errorPage = resolvedSiteMapItem .getHstComponentConfiguration() .getParameter("unauthorized"); } if(componentException instanceof DocumentNotFoundException || componentException.getCause() instanceof DocumentNotFoundException) { errorPage = resolvedSiteMapItem .getHstComponentConfiguration() .getParameter("notfound"); } try { if(errorPage != null) { hstResponse.forward(errorPage); return Status.HANDLED_TO_STOP; } } catch (IOException e) { log.warn("Failed to forward page: {}. {}", errorPage, e.toString()); } } } return Status.HANDLED_BUT_CONTINUE; } protected void logWarningsForEachComponentExceptions( PageErrors pageErrors) { for (HstComponentInfo componentInfo : pageErrors.getComponentInfos()) { for (HstComponentException componentException : pageErrors.getComponentExceptions(componentInfo)) { if (log.isDebugEnabled()) { log.debug("Component exception found on " + componentInfo.getComponentClassName(), componentException); } else if (log.isWarnEnabled()) { log.warn("Component exception found on {}: {}", componentInfo.getComponentClassName(), componentException.toString()); } } } }