In this article, we will see how to extend Tapestry 5 default exception filter to handle multiple exception pages in function of the type of exception. This idea cames to me while trying Spring MVC and its exception handling.
First Redefine the RequestExceptionHandler service
We have to implement the RequestExceptionHandler interface, in this new implementation we will provide a way to contribute different page per exception. This articles explains how to implement this class step by step.
The constructor
public WookiRequestExceptionHandler(
Map<Class, String> exceptionMap,
RequestPageCache pageCache,
ComponentClassResolver classResolver,
PageResponseRenderer renderer,
Logger logger,
@Inject @Symbol(SymbolConstants.EXCEPTION_REPORT_PAGE) String pageName,
@Inject @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode,
@Inject @Symbol(WookiSymbolsConstants.ERROR_WOOKI_EXCEPTION_REPORT) String wookiErrorPage,
Response response) {
this.exceptionMap = exceptionMap;
this.pageCache = pageCache;
this.renderer = renderer;
this.logger = logger;
this.pageName = pageName;
this.response = response;
this.classResolver = classResolver;
this.productionMode = productionMode;
this.wookiErrorPage = wookiErrorPage;
}
Parameters
- The first parameter is used to allow the user of the service to provide a different page per exception
- The ‘pageName’ parameter is used to obtain the default tapestry exception report page that will always be used in development mode
- The ‘productionMode’ is used to detect if we have to use the development exception page or use the ones contributed by the user
- The ‘wookiErrorPage’ is our own default exception page
- The other parameters are used in the main method of the RequestExceptionHandler
Now we have all the elements to implement the core method of the service.
The handleRequestException method
This method has been a lot extended from Tapestry 5 default one, only the page selection mechanism has changed.
public void handleRequestException(Throwable exception) throws IOException {
String exceptionPage = this.pageName;
if(this.productionMode) {
exceptionPage = wookiErrorPage;
}
logger.error("An exception has occured", exception);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setHeader("X-Tapestry-ErrorMessage", InternalUtils.toMessage(exception));
// Check if there is an existing a page that correspond to the root exception
if (exception.getCause() != null && this.exceptionMap.containsKey(exception.getCause().getClass())) {
String page = this.exceptionMap.get(exception.getCause().getClass());
if (classResolver.isPageName(page)) {
exceptionPage = page;
}
}
Page page = pageCache.get(exceptionPage);
ExceptionReporter rootComponent = (ExceptionReporter) page.getRootComponent();
// Let the page set up for the new exception.
rootComponent.reportException(exception);
renderer.renderPageResponse(page);
}
Explanations
- First we set the exception page to wooki’s default one if production mode is set
- After HTTP status and tapestry error header are set
- Then we get the cause of the exception from the TapestryException and then we check in the map if we have page corresponding to this exception
- Now you have the exception page, we get it from requestPageCache, call the reportException and simply render it
I guess the selection mechanism should be extended by you to better suit to your needs. Now we have implemented the service we have to modify the AppModule class to declare and map exception to page names.
Create your TapestryOverrideModule class
Doing a special Module class to extend Tapestry default one will allow us to lower our own AppModule class and focus on Tapestry 5 stuff in this class
public class TapestryOverrideModule {
public static void bind(ServiceBinder binder) {
binder.bind(RequestExceptionHandler.class,
WookiRequestExceptionHandler.class).withId("WookiRequestExceptionHandler");
}
/**
* Wooki Symbols default
*/
public static void contributeFactoryDefaults(MappedConfiguration<String, String> configuration) {
configuration.add(WookiSymbolsConstants.ERROR_WOOKI_EXCEPTION_REPORT, "error/generic");
}
/**
* Alias default request handler.
*
* @param exceptionHandler
* @param configuration
*/
public static void contributeAlias(
@InjectService("WookiRequestExceptionHandler") RequestExceptionHandler exceptionHandler,
Configuration<AliasContribution> configuration) {
configuration.add(AliasContribution.create(RequestExceptionHandler.class, exceptionHandler));
}
}
Explanations
- The first method ‘bind’ is used to declare our new service via the ServiceBinder of Tapestry, this will automatically build a proxy to access to our service instance. Every elements annotated with @Inject in the constructor will populated automatically by the ServiceBinder service. It’s really important to define your own id to avoid conflict with default one.
- Then we contribute a default generic exception page for our application
- The last method is used to replace Tapestry 5 default RequestExceptionHandler service by our custom one
Now we have to load this module and configure the RequestExceptionHandler with key/value pairs of exception and pages.
Update the ‘AppModule’ class
</pre>
@SubModule(TapestryOverrideModule.class)
public class AppModule {
public void contributeWookiRequestExceptionHandler(MappedConfiguration<Class, String> exceptionMap) {
exceptionMap.add(IllegalArgumentException.class, "IAEReport");
}
}
Explanations
- Use SubModule annotation to include the TapestryOverrideModule
- Then contribute to the WookiRequestExceptionHandler to map exception to page name
Conclusion
As we have seen Tapestry 5 allow us to completely override default mechanism by our own. Of course this implementation has been designed to suit to our needs, and you will probably want to create your own selection mechanism. By the way, this article has demonstrated the principles to extend the Tapestry exception mechanism, now it’s up to you !
Thanks for reading
Christophe.


[...] This post was mentioned on Twitter by Matthieu, spread the source. spread the source said: blog post: Handle multiple exceptions pages with #tapestry 5 . http://bit.ly/5kkAN1 [...]
This is really great !
Thank you :)
Little complement: the IAEReport exceptions page needs to implement ExceptionReporter.