views:

422

answers:

3

Hello

I wonder if anyone can advise: I have a scenario where a scheduled job being run by Quartz will update an arraylist of objects every hour.

But I need this arraylist of objects to be visible to all sessions created by Tomcat. So what I'm thinking is that I write this object somewhere every hour from the Quartz job that runs so that each session can access it.

Can anyone say how best this may be achieved? I was wondering about the object being written to servlet context from the Quartz job? The alternative is having each session populate the arraylist of objects from a database table.

Thanks

Mr Morgan.

A: 

Well, if you use static fields they will be visible to all classes loaded by the same class loader. I think that at least the servlets of one app should end up qualifying. However, this is admittedly dirty.

An object that is defined and guaranteed to be (more) global is the ServletContext. This is shared between all servlets forming part of one application, i.e. loaded from the same web.xml. There are put and get calls for ServletContext that allow you treat it essentially as a Map.

Beyond that, you'll need to find classes common to all Web apps inside one Tomcat server. Tomcat does a lot of footwork with loaders, and I think different Web apps will have distinct loaders. You can get around this by writing a class of your own and placing that class in Tomcat's common or shared directories. If I understand this description correctly, those classes will be made available, ONCE, to all Web apps.

Finally, beyond the confines of a single Tomcat server, you'll need some TCP/IP based mechanism for communicating between JVMs. But as I understood your question, that shouldn't be necessary.

Carl Smotricz
But can you access ServletContext outside of a servlet? The Quartz job i mention is one that implments an interface of theirs. I am prepared to use a class with statics though.
Mr Morgan
Hmm. Is this Quartz job running as a servlet or filter inside Tomcat? If so, yes. If it's being fired up in an external JVM it will have trouble looking into Tomcat's brain. Looking at [this Quartz doc](http://www.quartz-scheduler.org/docs/cookbook/ServletInitScheduler.html), it looks like Quartz will be inside your app, though, and all should be well.
Carl Smotricz
At least Quartz gets access to your ServletContext (in both of the scenarios described). I'd hope Quartz then has the good sense to make that context accessible to its API users as well.
Carl Smotricz
I think the Quartz job runs inside Tomcat because a ServletContextListener initiates a Quartz ScheduleController which 'submits' the job in question.
Mr Morgan
Yep, I'm glad to see you're implementing one of the recommended scenarios. Do I have to go digging in the Quartz docs now? I'll be back...
Carl Smotricz
Thanks. Any advice is welcome.
Mr Morgan
Hmm, the doc on Quartz under a servlet environment are not very forthcoming. I'm essentially down to guessing, based on the Quartz API. Ah... your work unit, the Job, implements the `Job` interface. It gets a `JobExecutionContext`. Chances are, you can use that context's `put` and `get` methods to communicate arbitrary data via job instances in the container. Try it!
Carl Smotricz
Thanks for your help.
Mr Morgan
Assuming I set it up correctly, it failed. I ran a job to populate a variable in the Quartz jobExecutionContext, and used a servlet to access the variable from ServletContext after. But the variable was null.
Mr Morgan
Oops... I'd missed that you want to access the values from an activity that isn't a Quartz job. No, the execution context and the servlet context are not the same thing. I think you should bite the bullet and use the static var in `/shared`.
Carl Smotricz
A: 

Yes, I would store the list in the ServletContext as an application-scoped attribute. Pulling the data from a database instead is probably less efficient, since you're only updating the list every hour. Creating a ServletContextListener might be necessary in order to give the Quartz task a reference to the ServletContext object. The ServletContext can only be retrieved from JavaEE-related classes like Servlets and Listeners.

EDIT: In the ServletContextListener, when you create the job, you can pass the list into the job by adding it to a JobDataMap.

public class MyServletContextListener extends ServletContextListener{
  public void contextInitialized(ServletContextEvent event){
    ArrayList list = new ArrayList();

    //add to ServletContext
    event.getServletContext().setAttribute("list", list);

    JobDataMap map = new JobDataMap();
    map.put("list", list);
    JobDetail job = new JobDetail(..., MyJob.class);
    job.setJobDataMap(map);
    //execute job
  }

  public void contextDestroyed(ServletContextEvent event){}
}

//Quartz job
public class MyJob implements Job{
  public void execute(JobExecutionContext context){
    ArrayList list = (ArrayList)context.getMergedJobDataMap().get("list");
    //...
  }
}
Michael Angstadt
So what you're saying is that I have a listener that has a method in it to populate the object; this listener runs when the context is initialised and populates the object. The Quartz job can then get at the object via the listener when it runs every hour?
Mr Morgan
Looking at a comment from another answer, it looks like you already have a ServletContextListener that starts the Quartz task? What you can do is create the list in the listener, then add the list as an attribute to the ServletContext, then pass that same list to the task. You can then access the list anywhere in your web application by calling `ServletContext#getAttribute()`. For example, in a servlet, you'd call `(ArrayList)getServletContext().getAttribute("list")`. Make sure you properly synchronize your list so that you're not reading/writing to it as the same time.
Michael Angstadt
This sounds good but with one exception. I'm not sure if 'then pass that same list to the task' is possible given the way in which the jobs are submitted by Quartz's ScheduleController.
Mr Morgan
You just have to somehow make sure that the ServletContext and the Quartz task both have a reference to the same ArrayList object that you want to use in your web application. If the ArrayList is created in the task, maybe you could pass the task a reference to the ServletContext object and make the task add the list to the ServletContext.
Michael Angstadt
What I've just tried is creating a method in the ServletContextListener which returns an integer. When the Quartz job runs, it creates an instance of the ServletContextListener and calls the method. It seems to work but needs testing. Replacing the integer with an arraylist and having the ServletContextListener's method repopulate the list, and synchronising this, may solve the problem.
Mr Morgan
You should never create a new instance of a listener. Only the Container should do that. What does to the code for your Quartz job look like? (sorry, I'm not familiar with Quartz) Is the job a class that extends some Quartz class? If so, you can just create a setter method in the job class which gives the job a reference to the list. Something like: `ArrayList list = new ArrayList(); QuartzJob job = new QuartzJob(); job.setList(list); servletContext.setAttribute("list", list);`
Michael Angstadt
With Quartz, the jobs are submitted by defining the job and a trigger, and then making a call to a method called: scheduler.scheduleJob(jd, ct); where jd is the job and ct is a cron trigger. The jobs submitted implement an interface of quartz's called Job via a method called execute.
Mr Morgan
http://www.quartz-scheduler.org/docs/examples/Example3.html gives a good few examples.
Mr Morgan
What if you put the ArrayList in a `JobDataMap`? Then, you could access the JobDataMap from the JobExecutionContext in the `execute()` method and then get the list.
Michael Angstadt
I never saw this before. Converting the arraylist to a map could allow the map to be built up within the job. But doesn't it leave the problem of setting the list / map to ServletContext?
Mr Morgan
You don't need to convert anything to a map. (1) Create the ArrayList in the listener. (2) Add the list to the ServletContext. (3) Add the list to a new JobDataMap object. (4) Add that JobDataMap object to the JobDetail object. `servletContext.setAttribute("list", list); JobDataMap map = new JobDataMap(); map.put("list", list); JobDetail jd = ...; jd.setJobDataMap(map);`
Michael Angstadt
Then, access the list from the job class' `execute()` method. `ArrayList list = (ArrayList)context.getMergedJobDataMap().get("list");`
Michael Angstadt
I follow you but the problem is that the arraylist needs to be updated by the job. and servlet context is not accessible from the job itself.
Mr Morgan
Right, the list gets added to the ServletContext in the ServletContextListener. Both the ServletContext and the job will have a reference to the same list object. So when the job updates the list, the list in the ServletContext will also be updated.
Michael Angstadt
So updates to the JobMap within the job will appear in the list object by virtue of passing by reference. I will try this tomorrow. Thanks.
Mr Morgan
Ok, let me know how it goes.
Michael Angstadt
At the moment, time constraints dictate that I use database table for storage. But I will replace this later with a SessionListener which populates my arraylist. Any method needing to access the arraylist can then read it via a static method in the Listener's class. But I'd still like to use ServletContext.
Mr Morgan
See the edits to my answer for a code sample...I don't see how this wouldn't work.
Michael Angstadt
Many thanks. I will try this when I have the time.
Mr Morgan
A: 

You can try some caching solution, like EhCache to store you values, and update them every hour. It will handle concurrency issues. The cache object itself can be stored in the ServletContext

A good way to write to the ServletContext from the Quartz job is to register listeners to your job that get notified about the changed value. So for example:

public class JobListener {
    public void updateValue(Object newValue);
}

public class ServletContextCacheJobListener implements JobListener {
     private ServletContext ctx;
     public ServletContextJobListener(ServletContext ctx) {
         this.ctx = ctx;
     }

     public void updateValue(Object newValue) {
          Cache cache = (Cache) ctx.getAttribute("cache");
          cache.update("yourKey", newValue);
     }
}

Your Job will have a List<JobListener> and when you schedule the job, you instantiate the concrete listener and add it to the job.

Bozho
Not sure I understand quite where you're coming from. Can you clarify? Are you saying that my Quartz job should have a list of JobListener or ServletContextCacheJobListener? If the latter, presumably I would then set the list one element at a time?
Mr Morgan
Read about the Observer (listener) pattern. And `ServletContextCacheJobListener` is a `JobListener` because it implements it. So there is no difference
Bozho