Grails: Prevent further code execution after a redirect or forward in base class of a controller
I have following controller structure:
abstract class AbstractController {
// ...
}
abstract class AbstractDiagramController extends AbstractController {
// ...
}
class PopulationController extends AbstractDiagramController {
// ...
}
Most controller-actions call various methods of the abstract base classes. If now one of these base-methods needs to send a redirect (or forward) to the client, Grails won't prevent the application from processing the remaining action code of the controller-action anyway.
From my point of view this is an undesirable behaviour, due the base-methods do some kind of validation (like validating parameter, user, session etc), and the controller assumes that the validation succeeded (thence produces subsequent errors).
How can i prevent this insufficient behaviour?
Kind regards, Christopher
PS: I found already this question, but the answers did not satisfy my needs, because none of them deal with a base controller:
PPS: I am using Grails in version 1.3.7
EDIT
This is a reaction of Victor Sergienko's comments. Here I give a more detailled code-example of my problem.
// The very base controller
abstract class AbstractController {
// The interceptor gets called before any action of inheritors get processed
def beforeInterceptor = [action:this.&initialize]
// Method validates various stuff
protected initialize() {
if( someThingIsWrong() ) {
// This redirect should stop any other code of inheritors
redirect( controller: "foo", action: "bar" )
return false
}
}
}
// The second base controller
abstract class AbstractDiagramController extends AbstractController {
// This object must get initialized. If not (eg any errors or exceptions occured)
// all inheritors actions are not allowed to do anything
MyObject myGreatObject = null
// Overriden, because of additional individual diagram validation
@Override
protected initialize() {
// Do parents stuff first
super.auth()
// If parent failed, this code should not get executed anymore.
// Yes, here I could check if parent returned false and return false as well before
// continuing the next validation. Anyway I have to do this because grails, comprehendibly,
// will throw an exception if two redirects were executed in a row.
// (With this I just want to visualize the behaviour I'd expect)
if( someThingElseIsWrong() ) {
redirect( controller: "hello", action: "world")
return false
}
// After validation I can initialize the object
myGreatObject = new My开发者_运维问答Object()
}
}
// A controller implementation
class MyDiagramController extends AbstractDiagramController {
// Overriden because of inidividual validation
@Override
protected initialize() {
// First do parent stuff
boolean succeeded = super.auth()
// Again, annoying double check
if( !succeeded ) {
return false
}
}
def myAction = {
myGreatObject.SpinAroundAndBeHappy()
}
}
Looks like it was a good idea to reduce the use-case to the minimum lines of code. Now it seem like Victor's suggestions (either canContinue
or hasErrors
) could solve this unpleasant circumstances somehow, even though it's some kind of workaround.
But somehow I don't like those double-checks. I'm still struggling against the fact, that all layers above the abstract base controller have to react on invalid validations that happened already before (and also should be managed by the base controllers on their own). From my point of view those checks should not be the business of the controller implementations.
PS: I hope no grave mistakes have slipped in the code.
As a workaround, you can return a boolean canContinue
from an ancestor action or throw an exception, or check instance.hasErrors()
in your case.
EDIT: The fact initialize()
is called before an action looks like access control or another complete override of action semantics (before any part of action is executed). Please tell if my assumption is wrong.
When we did a custom security access to different actions, we annotated the actions with own tags like @SecuredDoodah
(see @Secured), and added a Filter
that completely overrides the action (for us, Filter
responds with 403, but it's not necessary).
Filter
might be better than beforeInterceptor
. If you need to pass some state from Filter
, like myGreatObject
in the sample, you can inject a Service into Filter and save the state in the Service.
I'm sure there are better ways then my idea, but this should work transparently for Controllers.
You're limited by the fact that this is Java/Groovy and there's no way for a method call to immediately trigger an exit from a method (or Closure). I saw that another framework cheats and when you call render, redirect, etc. it throws an exception that's caught it the framework base class. This acts like a Goto, which doesn't really exist.
It's an expensive approach though - filling in all those stack frames unnecessarily is wasteful since it's not an exception case and the stack will always be ignored.
Unfortunately you need an approach like Victor's where you use boolean return values.
精彩评论