views:

56

answers:

3

Basically, I want to be able to assign objects created within filters to members in a base controller from which every controller extends. Any possible way to do that?

Here's how I tried, but haven't got to make it work.

What I'm trying to achieve is to have all my controllers extend a base controller. The base controller's constructor would be used to assign values to its members, those values being pulled from the session map. Example below.

File grails-app/controllers/HomeController.groovy:

class HomeController extends BaseController {
    def index = {
        render username
    }
}

File grails-app/controllers/BaseController.groovy:

abstract class BaseController {
    public String username

    public BaseController() {
        username = session.username
    }
}

When running the app, the output shown is:

2010-06-15 18:17:16,671 [main] ERROR [localhost].[/webapp]  - Exception sending context initialized event to listener instance of class org.codehaus.groovy.grails.web.context.GrailsContextLoaderListener
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'pluginManager' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Invocation of init method failed; nested exception is java.lang.RuntimeException: Unable to locate constructor with Class parameter for class org.codehaus.groovy.grails.commons.DefaultGrailsControllerClass
    ...
Caused by: java.lang.RuntimeException: Unable to locate constructor with Class parameter for class org.codehaus.groovy.grails.commons.DefaultGrailsControllerClass
    ...
Caused by: java.lang.reflect.InvocationTargetException
    ...
Caused by: org.codehaus.groovy.grails.exceptions.NewInstanceCreationException: Could not create a new instance of class [com.my.package.controller.HomeController]!
    ...
Caused by: groovy.lang.MissingPropertyException: No such property: session for class: com.my.package.controller.HomeController
    at com.my.package.controller.BaseController.<init>(BaseController.groovy:16)
    at com.my.package.controller.HomeController.<init>(HomeController.groovy)
    ...
2010-06-15 18:17:16,687 [main] ERROR core.StandardContext  - Error listenerStart
2010-06-15 18:17:16,687 [main] ERROR core.StandardContext  - Context [/webapp] startup failed due to previous errors

And the app won't run.

This is just an example as in my case I wouldn't want to assign a username to a string value, but rather a few objects pulled from the session map. The objects pulled from the session map are being set within filters.

The alternative I see is being able to access the controller's instance within the filter's execution. Is that possible?

Please help! Thanks a bunch!

+2  A: 

You typically can't do much in the constructor in Grails artifacts. You can use an interceptor for this though:

abstract class BaseController {
   protected String username

   def beforeInterceptor = {
      username = session.username
   }
}

This is described in section 6.1.5 of http://grails.org/doc/latest/

Burt Beckwith
Thanks for your answer! Problem with this is when a concrete controller (HomeController) defines a beforeInterceptor itself, the BaseController's beforeInterceptor won't get called. Trying to call it using 'super.beforeInterceptor()' through the concrete controller compiles, but upon runtime throws this:org.codehaus.groovy.runtime.InvokerInvocationException: java.lang.StackOverflowError at java.lang.Thread.run(Thread.java:619)Caused by: java.lang.StackOverflowError at com.my.package.controller.HomeController$_closure1.doCall(HomeController.groovy) ...Any way to fix that? Thanks!
4h34d
Since it's a Closure, super.beforeInterceptor() can't work. But you can have the interceptor delegate to a real method which can be overridden:def beforeInterceptor = { prepareCall() }protected void prepareCall() { username = session.username }
Burt Beckwith
A: 

You can use a request scoped service...

Nick
A: 

As a rule, I'd caution against putting state directly in a controller; controllers in general (regardless of framework) are generally intended to be stateless. I'd stick with the standard webapp state constructs like request and session to store and transfer data.

For your specific case, I'd do one of the following:

  • If lightweight data (primitives), I'd store them in the session and just access them as needed:
    e.g. render session.username

  • If dynamic or database-driven data, I'd create a service and pull the data as needed: e.g. homeService.getUser().username

  • As a variation to your discussion with Burt above, you could use a filter to populate a request or session value as well.

ecodan