2

I am attempting to create functionality in a JSF1.2/ADF web app that will periodically & dynamically generate a sitemap for a website that will have hundreds of pages whose content will change daily. The catch is that I need to read some config from the application to use as the basis of the sitemap and to do so, I need FacesContext.

Here is what I have attempted to do: I created a class that implements a ServletContextListener and instantiates an application scoped bean. This bean does the heavy lifting to create sitemap.xml using FacesContext. I created a class that extends TimerTask that accesses the bean from application scope, calls the sitemap method and schedules future occurrences. When I run the application, the class that implements ServletContextListener fires and the bean appears to be created, but the class that extends TimerTask is never fired. Any help would be appreciated. If I can answer any questions or if I left anything out, please let me know.

Here are my code samples:

public class WebhomesApplicationContextListener implements ServletContextListener {
 private static final String attribute = "SiteMapGenerator";
  public void contextInitialized(ServletContextEvent event) {
  SiteMapGenerator myObject = new SiteMapGenerator();
  event.getServletContext().setAttribute(attribute, myObject);
 }
 public void contextDestroyed(ServletContextEvent event) {
  SiteMapGenerator myObject = (SiteMapGenerator) event.getServletContext().getAttribute(attribute);
  event.getServletContext().removeAttribute(attribute);
 }
}

public class SiteMapGenerator {
 public void generateSitemap() {
   // code to generate map...
 }
}

public class Scheduler extends TimerTask {
 public void run() {
  SiteMapGenerator sitemap = (SiteMapGenerator)FacesContext.getCurrentInstance().getExternalContext().getApplicationMap().get("SiteMapGenerator");
  sitemap.generateSitemap();
 }
}

class MainApplication {
 public static void main(String[] args) {
  Timer timer = new Timer();
  timer.schedule(
   new Scheduler(),
   1000 * 60);
 }
}
MattC
  • 59
  • 10

1 Answers1

2

No, you can't. The FacesContext is only available in the thread associated with the HTTP servlet request whose URL matched the URL pattern of the FacesServlet and has invoked it. Instead, just pass the SiteMapGenerator to the Scheduler on its construction.

public class Scheduler {

    private SiteMapGenerator sitemap;

    public Scheduler(SiteMapGenerator sitemap) {
        this.sitemap = sitemap;
    }

    // ...
}

The SiteMapGenerator is surely available at the point you're constructing the Scheduler.


Unrelated to the concrete problem, It's strongly discouraged to use TimerTask in a Java EE application. See also Spawning threads in a JSF managed bean for scheduled tasks using a timer.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks for the link, was actually looking at it this afternoon to switch over to ScheduledExecutorService. Given your response, I am not sure how/when to construct the scheduler since I need to it be enabled automatically upon deployment. – MattC Dec 07 '11 at 23:57
  • There are examples in the link. You'll only need XML instead of annotations to register either the application scoped bean or the servlet context listener. – BalusC Dec 08 '11 at 01:29
  • Maybe my Java newbie-ness is hindering me on this one... Using the example in the link you provided, I created a "BackgroundJobManager" application scoped bean that instantiates the "SomeDailyJob" class in its init() method. On application load, the BackgroundJobManager init() method is not fired. With ServletContextListener not being an option, I am stumped on how to get "BackgroundJobManager" to process on app startup. Here is my [sample code](http://pastebin.com/SMX26Lt7) – MattC Dec 09 '11 at 15:23
  • In JSF 1.x you're going to need to reference this application scoped bean as a managed property of some common request or session scoped bean, or maybe in some common view template. Why is `ServletContextListener` not an option by the way? – BalusC Dec 09 '11 at 15:32
  • Thank you for your continued assistance on this. I assumed ServletContextListener wasn't an option because of my initial failure and my interpretation of your answer. So by "reference this application scoped bean as a managed property of some common request or session scoped bean, or maybe in some common view template", for example, I could call the bean in the beforePhase method of the home page view. Since I wouldn't want it to fire each time, seems like I would have to do a check so that it's not called each time a user hits the home page. – MattC Dec 09 '11 at 15:37
  • I am still struggling with this one. As you can see in my [latest code](http://pastebin.com/hDLURWsB), I instantiate BackgroundJobManager from a page PhaseEvent and pass the SiteMapGenerator object to it on creation as suggested above. I then call BackgroundJobManager.init() and SiteMapGenerator is called. So far, so good. Then, when SiteMapGenerator is processed, the first reference to FacesContext is encountered and debugging stops, no errors are thrown and the page loads as if nothing negative occured. Very odd. – MattC Dec 09 '11 at 20:31
  • The whole point is that you should and can **not** access the `FacesContext` anywhere *inside* your `Scheduler` runnable class. Regardless of it's been fired by a `ServletContextListener` or an application scoped bean. The `FacesContext` is only available in the HTTP request thread which has invoked the `FacesServlet`, not in any other arbitrary thread. – BalusC Dec 09 '11 at 20:35
  • Ah, now I see. I was thinking you meant I could access `FacesContext` inside the `Scheduler` runnable. With that straight, [here](http://pastebin.com/Yidj1Eak) is how i have it working. Thank you for seeing me through on this. – MattC Dec 09 '11 at 22:15