Grails allows you to perform custom , fine-grained Exception handling for error conditions in your controllers. As per usual, you can take advantage of this feature by making use of Grails' nifty Dependency Injection features. In this case, we will depend on the
GrailsExceptionResolver class being injected into /conf/BootStrap.groovy as "exceptionHandler".Note: This article shows you a fine-grained alternative to the "500 Internal Server" catch-all which is generated by default in /conf/UrlMappings.groovy
The important changes are in the below bit of code on lines 3, and 7 to 9 of my BootStrap.groovy file:
1:class BootStrap {
2:
3: def exceptionHandler
4:
5: def init = { servletContext ->
6:
7: exceptionHandler.exceptionMappings =
8: [ 'NoSuchFlowExecutionException' :'/my/doIt',
9: 'java.lang.Exception' : '/error']
10:
11: }
12:
13: def destroy = { }
14:}
In the above code snippet we set the exceptionHandler's exceptionMappings property to a Map containing the name of the exception we wish to handle, mapped to the URI we wish to handle our error condition.
Line 9 above is very important. This is our "catchall" for all other java.lang.Exceptions to be handled by Grails' internal error handler. It is worth noting that leaving out "java.lang" will have the incorrect bevaviour. Taken from the javadoc for GrailsExceptionResolver's setExceptionMappings() method:
NB: Consider carefully how specific the pattern is, and whether to include package information (which isn't mandatory). For example, "Exception" will match nearly anything, and will probably hide other rules. "java.lang.Exception" would be correct if "Exception" was meant to define a rule for all checked exceptions. With more unusual exception names such as "BaseBusinessException" there's no need to use a FQN.
NOTE: In the above code the URI maps onto our urlMappings, therefore
- '/my/doIt' - invokes doIt closure on MyController
- '/error' - skips straight to /views/error.gsp
Lastly, in order for all the above to work, we must remove the default "500" error handler from your /conf/UrlMappings.groovy as follows (line 8):
1:class UrlMappings {
2: static mappings = {
3: "/$controller/$action?/$id?"{
4: constraints {
5: // apply constraints here
6: }
7: }
8:// "500"(view: null)
9: }
10:}
- Spring 2.5 API Documentation for SimpleMappingExceptionResolver
- Grails 1.0.1 API Document for GrailsExceptionResolver
14 comments:
Thanks for this - I was just trying to figure out how to this - now I have nice error handling for NoFlow exceptions!
Hi Stephan,
This looks interesting. However, I cannot get it to call the controller closure. Does this closure need to have a special format or something?
if I add to the Bootstrap:
exceptionHandler.exceptionMappings =
[ 'RuntimeException' :'/internal/handleException',
'Exception' :'/internal/handleException' ]
and define in InternalController:
def handleException = {
println(params)
render(text:params, contentType:"text/plain")
}
I get this result:
HTTP ERROR: 404
/WEB-INF/grails-app/views/internal/handleException.jsp
So it looks like it cannot find the controller closure (or it doesn't even try to find it and tries to jump straight to the view)
Any ideas?
Hi rintcius
It looks like your handler is working fine.
Did you check and make sure that you actually have a .gsp called
/WEB-INF/grails-app/views/internal/handleException.gsp
Cheers
Stephan
Thanks for your response Stephan.
Yes, the exceptionHandler does work to the extent that if I add a gsp then it finds and returns that.
What I was referring to is that you mentioned that it can call the controller closure which I cannot get working, since it always seems to jump straight to the view and ignores the controller (that's why I called render with the text parameter so that it would return the text directly instead of something in the view)
The exception handler doesn't call the controller closure from the URL mappings instead it is looking for the gsp
Thanks for your message, Shyam. I also thought it did, so your confirmation is helpful.
So you can see it in development or have a custom one to have your testers report what's up with your bad code.
have some grails stuff on blog.doylecentral.com
import grails.util.GrailsUtil
class BootStrap {
def exceptionHandler
def init = { servletContext ->
switch (GrailsUtil.environment)
{
break
case "test":
exceptionHandler.exceptionMappings =
[ 'NoSuchFlowExecutionException' :'/gas/error',
'Exception' : '/error']
break
case "production":
exceptionHandler.exceptionMappings =
[ 'NoSuchFlowExecutionException' :'/gas/error',
'Exception' : '/error']
break
}
}
Hey there Stephan,
Did you ever test what you described?
> '/my/doIt' - invokes doIt
> closure on MyController
If you did test it - which version of Grails were you using? I've tried 1.0.3 with no avail. The problem could be related to the bug I've recently reported.
I was never able to invoke the controller directly. As a work around, I configured a '500' handler in my UrlMappings file, and then detected the type of exception that caused the error. If the 500 was due to a 'NoSuchFlowExecutionException' I'd then redirect to a GSP from within the flow. More details on that here.
Brock
Hello Brock
I've updated the code snippet. It seems that the behaviour for GrailsExceptionResolver has changed subtly so that one now has to fully qualify the "java.lang.Exception" catchall. Failing to do the above qualification will lead to the "catchall" always catching any Exceptions ending in *Exception
Stephan
How do I access the actual exception in /my/doIt?
Thanks
Hi Stephan,
Have you tried this exception handling with Grails 1.1 and web flow?
It seems like the behavior has changed in 1.1. When the flow or session expires, I always seem to get redirected to a new flow and no exception is thrown.
thanks,
tom
Hi Tom
Nope, have not tried with the latest version. Thanks for the heads-up. I'll have a look at it.
Cheers
Stephan
Hi Stephen,
This handling doesnt seem to work. It doesnt actually call an action in the controller, it just looks for a view within the views directory.
Is there anyway to actually call a controller action using this type of handleing?
Thanks
Christian
I think this is the simplest solution:
1. Add a mapping entry to UrlMappings.groovy
'500':(view:'error')
2. Under grails-app/views, create a file named error.gsp, in which you write your custom error msg
Post a Comment