16

I'm building a JSF+Facelets web app, one piece of which is a method that scans a directory every so often and indexes any changes. This method is part of a bean which is in application scope. I have built a subclass of TimerTask to call the method every X milliseconds. My problem is getting the bean initialized. I can reference the bean on a page, and when I go to the page, the bean is initialized, and works as directed; what I would like instead is for the bean to be initialized when the web context is initialized, so that it doesn't require a page visit to start the indexing method. Google has shown a few people that want this functionality, but no real solutions outside of integrating with Spring, which I really don't want to do just to get this piece of functionality.

I've tried playing around with both the servlets that have "load-on-startup" set, and a ServletContextListener to get things going, and haven't been able to get the set up right, either because there isn't a FacesContext available, or because I can't reference the bean from the JSF environment.

Is there any way to get a JSF bean initialized on web app startup?

Matt McMinn
  • 15,983
  • 17
  • 62
  • 77

3 Answers3

15

If your code calls FacesContext, it will not work outside a thread associated with a JSF request lifecycle. A FacesContext object is created for every request and disposed at the end of the request. The reason you can fetch it via a static call is because it is set to a ThreadLocal at the start of the request. The lifecycle of a FacesContext bears no relation to that of a ServletContext.

Maybe this isn't enough (it sounds like you've already been down this route), but you should be able to use a ServletContextListener to do what you want. Just make sure that any calls to the FacesContext are kept in the JSP's request thread.

web.xml:

<listener>
    <listener-class>appobj.MyApplicationContextListener</listener-class>
</listener>

Implementation:

public class MyApplicationContextListener implements ServletContextListener {

    private static final String FOO = "foo";

    public void contextInitialized(ServletContextEvent event) {
        MyObject myObject = new MyObject();
        event.getServletContext().setAttribute(FOO, myObject);
    }

    public void contextDestroyed(ServletContextEvent event) {
        MyObject myObject = (MyObject) event.getServletContext().getAttribute(
                FOO);
        try {
            event.getServletContext().removeAttribute(FOO);
        } finally {
            myObject.dispose();
        }
    }

}

You can address this object via the JSF application scope (or just directly if no other variable exists with the same name):

<f:view>
    <h:outputText value="#{applicationScope.foo.value}" />
    <h:outputText value="#{foo.value}" />
</f:view>

If you wish to retrieve the object in a JSF managed bean, you can get it from the ExternalContext:

FacesContext.getCurrentInstance()
            .getExternalContext().getApplicationMap().get("foo");
McDowell
  • 107,573
  • 31
  • 204
  • 267
  • This ended up working out - I was missing the "setAttribute" bit to make it accessible in the JSF code. Thanks! – Matt McMinn Dec 01 '08 at 16:44
0

In JSF 2+ you can use a SystemEventListener to handle it. You would set it to take action on the PostConstructApplicationEvent to initialize it.

<system-event-listener>
    <system-event-listener-class>
     listeners.SystemEventListenerImpl
    </system-event-listener-class>
    <system-event-class>
     javax.faces.event.PostConstructApplicationEvent
    </system-event-class>                       
</system-event-listener>

The implementation would look something like :

public class SystemEventListenerImpl implements SystemEventListener {

  @Override
  public void processEvent(SystemEvent event) throws AbortProcessingException {
    Application application = (Application) event.getSource();
   //TODO
  }

  @Override
  public boolean isListenerForSource(Object source) {
    return (source instanceof Application);
  }
}

This will allow you to do a lot more than just simply passing a value.

John Yeary
  • 1,112
  • 22
  • 45
  • 1
    Note that the OP is using JSF 1.x (JSF 2.x didn't exist when the question was asked). For JSF 2.x there's by the way a way much easier way: just a `@ManagedBean(eager=true) @ApplicationScoped`. No need for XML or interface mess. See also http://stackoverflow.com/questions/3600534/jsf-how-do-i-force-an-application-scoped-bean-to-instantiate-at-application-st/3600740#3600740 – BalusC Jul 15 '12 at 02:43
  • I went back and read the question, and your answer solves the last part of his question about eager loading for JSF 2.x. Good catch. – John Yeary Jul 16 '12 at 12:30
0

Using listeners or load-on-startup, try this: http://www.thoughtsabout.net/blog/archives/000033.html

Loki
  • 29,950
  • 9
  • 48
  • 62
  • I've used that trick before, but it doesn't work with ServletContextListener because you don't have a request/response for facesContext = contextFactory.getFacesContext(servletContext, request, response, lifecycle); It fails if any param is null. Same problem with load-on-startup/init. – Matt McMinn Nov 24 '08 at 19:47
  • I tmight be best/easier to just create you class at startup and store it. Then when the jsf bean is accessed for the first time, it will check for that class and use it or copy the data from one to the other. – Loki Nov 25 '08 at 22:45