CHAPTER 11

image

Batch Processing

Grails is more than just a web framework—it is an application framework. And almost all applications contain functionality that must be executed on a periodic basis (every 15 minutes, once an hour, twice a day, daily, weekly, month, quarterly, or yearly). This is known as batch processing. The Grails team anticipated the need for batch processing and decided to leverage a popular open source third-party enterprise job scheduling library: Quartz 1. Since the Spring Framework is a core component of Grails, and the Spring Framework already includes a Quartz integration, this was a natural choice. A Quartz Grails plugin makes it easy to use the Quartz library.

Quartz is similar to the Unixcron facility in that it provides the ability to execute a job in the future. However, Quartz is different from the Unixcron facility because it runs within the application server and has full access to all of the application components.

This chapter explores batch-processing functionality. We will start by installing the Quartz plugin and creating a simple job. Then we will move on to creating a sample batch­reporting facility.

Installing the Quartz Plugin

As we mentioned, Grails leverages Quartz for job-scheduling functionality. The Quartz plugin integrates Quartz into Grails and makes Quartz easy to use.

To begin, from within the project directory, execute the following command:

> grails install-plugin quartz

Or you can use the plugin manager of GGTS. This will yield the output as illustrated in Figure 11-1.

9781430248064_Fig11-01.jpg

Figure 11-1 .  Installing Quartz

Installing the plugin created the jobs directory under grails-app.

Creating a Job

A job is a program that contains the code you wish to run. In Grails, the job defines what to do and when to do it.

As a simple demonstration of Quartz in action, let’s create a job that prints a message and the current time. The first step is to create the job, as follows:

> grails create-job first

The command generates the FirstJob class, in the grails/job directory:

packagecollab.todo
classFirstJob {
static triggers = {
simple repeatInterval: 5000l // execute job once in 5 seconds
    }

defexecute() {
        // execute job
    }
}

image Note  Look closely at the timeout value, and you’ll see an l after the 5000. The l makes the variable a Long. Also notice that create-job follows conventions just like other create-* commands, and appends the suffix Job to the end of the job name.

The create-job command creates a skeleton job that is preconfigured to run once, five seconds after the application server starts. So, five seconds after the server starts, the code in the execute() method is executed. Add the following code to the execute() method:

println "Hello from FirstJob: "+ new Date()

Listing 11-1 shows the completed FirstJob class.

Listing 11-1.  Completed FirstJob

package collab.todo
class FirstJob {
static triggers = {
simple repeatInterval: 5000l // execute job once in 5 seconds
    }

def execute() {
println "Hello from FirstJob: "+ new Date()    }
}

Start the application by issuing the following command:

> grails run-app

While you could interpret the comment for the timeout property in Listing 11-1 to mean that the FirstJob is executed only once, you can see from the output in Figure 11-2 that it is executed every five seconds.

9781430248064_Fig11-02.jpg

Figure 11-2 .  Output every five seconds

Now that you’ve seen how to create a simple job, let’s move on to something a bit more useful: a batch-reporting facility.

Building a Batch-Reporting Facility

As an example, we will build a batch-reporting facility that generates todo reports and e-mails them to the user nightly. We will leverage a couple of services created in earlier chapters: EMailAuthenticatedService from Chapter 8 and ReportService from Chapter 10. Figure 11-3 shows an overview of the nightly reporting process.

9781430248064_Fig11-03.jpg

Figure 11-3 .  Nightly reporting process

The process starts with the NightlyReportJob. When the NightlyReportJob is invoked by Quartz, it immediately invokes the BatchService. The BatchService is the main control routine. It facilitates the interaction with other solution components. First, the BatchServiceretrieves all User objects that have an e-mail address. For each user, the BatchService retrieves the Todo objects. The BatchService then uses the ReportService to generate a PDF report. Finally, the BatchService uses the EmailAuthenticatedService to send the user an e-mail attachment of the report.

Building the batch-reporting facility requires the following steps:

  1. Create and configure the execution of the NightlyReportJob.
  2. Retrieve the user’s todos.
  3. Invoke the report service (created in Chapter 10 ).
  4. Invoke the e-mail service (created in Chapter 8 ).

Creating a Nightly Reporting Job

Issue the following command to create the NightlyReportJob:

> grails create-job NightlyReport

In addition to the timeout property, which you saw earlier in the FirstJob job, you can use several additional properties to control job execution.

Setting the Name and Group

You can use the name and group properties to help you identify jobs when interacting with the Quartz scheduler:

class NightlyReportJob {
    def name = "NightlyReport" // Job name
    def group = "CollabTodo"   // Job group

Controlling Execution Frequency

There are two techniques for controlling the job execution frequency:

  • Use the startDelay and timeout properties: these two properties allow you to control the execution frequency of the job. The startDelay property delays starting the job for a number of milliseconds after the application starts. This can be useful when you need to let the system start up before the job starts. Grails defaults the startDelay property to 0. The timeout property is the number of milliseconds between executions of the job. Grails defaults the timeout property to 60,000 milliseconds, or 1 minute.
  • Use the cronExpression property: for all of the Unix geeks out there, this works just as you would expect. It is a string that describes the execution frequency using a crontab format. If you’re not familiar with this approach, don’t worry—we’ll explain the format in more detail here.

Both techniques have their place in controlling execution frequency. Determining which technique to use depends on the job requirements. If the job can be handled by a timer, setting the startDelay and timeout properties should be sufficient, as in this example:

def startDelay = 20000     // Wait 20 seconds to start the job
def timeout = 60000        // Execute job once every 60 seconds

If the job is very time sensitive, using the cronExpression property is probably more appropriate. But note that during development and initial testing of the job, you will probably want to use the startDelay/timeout technique, and then switch to the cronExpression approach later.

image Caution  Depending on the execution frequency and duration of the job, it’s possible to have multiple instances of a job executing concurrently. This could happen if a job is long running and still running when the cronExpression property causes it be invoked again. Having jobs running concurrently may or may not be desirable. By default, the Quartz plugin permits the job to run concurrently. Most of the time, you probably won’t want to allow a job to run concurrently. You can change this behavior by setting the concurrent property on the job to false.

A cron expression tells the job scheduler when to run the job. The cronExpression property value is composed of six fields, separated by whitespace, representing seconds, minutes, hours, day, month, day of week, and an optional seventh field for the year. A cron expression expresses the fields left to right:

Seconds Minutes Hours DayOfMonth Month DayOfWeek Year

For example, we define a cronExpression property to have the job run 1:00 a.m. every day as follows:

def cronExpression = "0 0 1 * * *"  // Run every day at 1:00 a.m.

Table 11-1 describes the cron expression fields, and Table 11-2 summarizes some of the more commonly used special characters. (See the Quartz documentation for a more complete explanation of the special characters: http://www.quartz-scheduler.org/documentation/quartz-1.x/tutorials/crontrigger.)

Table 11-1. Cron Expression Fields

Field Values Special Characters
Seconds 0–59 , - * /
Minutes 0–59 , - * /
Hours 0–23 , - * /
DayOfMonth 1–31 , - * ? / L W
Month 1–12 or JAN–DEC , - * /
DayOfWeek 1–7 or SUN–SAT , - * ? / L#
Year (optional) Empty or 1970–2099 , - * /

Table 11-2. Cron Expression Special Characters

Character Function Example
* All values–matches all allowed values within a field. * in the Hours field matches every hour of the day, 0–23.
? No specific value–used to specify something in one of the two fields in which it is allowed, but not the other. To execute a job on the tenth day of the month, no matter what day of the week that is, put 10 in the DayOfMonth field and ? in the DayOfWeek field.
- Used to specify a range of values. 2–6 in the DayOfWeek field causes the job to be invoked on Monday, Tuesday, Wednesday, Thursday, and Friday.
, Used to create a list of values. MON, WED, FRI in the DayOfWeek field causes the job to be invoked on Monday, Wednesday, and Friday.
/ Used to specify increments. The character before the slash indicates when to start. The character after the slash represents the increment. 0/15 in the Minutes field causes the job to be invoked on the quarter hour—0, 15, 30, and 45 minutes.

Cron expressions are very powerful. With a little imagination, you can specify a multitude of times. Table 11-3 shows some sample cron expressions.

Table 11-3. Cron Expression Examples

Expression Meaning
0 0 1 * * ? Invoke at 1:00 a.m. every day
0 15 2 ? * * Invoke at 2:15 a.m. every day
0 15 2 * * ? Invoke at 2:15 a.m. every day
0 15 2 * * ? * Invoke at 2:15 a.m. every day
0 15 2 * * ? 2008 Invoke at 2:15 a.m. every day during the year 2008
0 * 13 * * ? Invoke every minute starting at 1 p.m. and ending at 1:59 p.m., every day
0 0/5 14 * * ? Invoke every 5 minutes starting at 2 p.m. and ending at 2:55 p.m., every day
0 0/5 14,18 * * ? Invoke every 5 minutes starting at 2 p.m. and ending at 2:55 p.m., and invoke every 5 minutes starting at 6 p.m. and ending at 6:55 p.m., every day
0 0–5 14 * * ? Invoke every minute starting at 2 p.m. and ending at 2:05 p.m., every day
0 10,45 14 ? 3 WED Invoke at 2:10 p.m. and at 2:45 p.m. every Wednesday in the month of March
0 15 2 ? * MON-FRI Invoke at 2:15 a.m. every Monday, Tuesday, Wednesday, Thursday, and Friday
0 15 2 15 * ? Invoke at 2:15 a.m. on the fifteenth day of every month
0 0 12 1/5 * ? Invoke at 12 p.m. (noon) every 5 days every month, starting on the first day of the month
0 11 11 25 12 ? Invoke every December 25 at 11:11 a.m.

Listing 11-2 shows the definition for the NightlyReportJob. Notice that it includes both techniques for controlling execution frequency, with the startDelay/timeout definitions commented out.

Listing 11-2.  NightlyReportJob Name, Group, and Execution Frequency Configuration

class NightlyReportJob {
  def cronExpression = "0 0 1 * * *" // Run every day at 1:00 a.m.
  def name = "Nightly"               // Job name
  def group = "CollabTodo"           // Job group

//  def startDelay = 20000           // Wait 20 seconds to start the job
//  def timeout = 60000              // Execute job once every 60 seconds

You can see why the Grails team chose to integrate Quartz instead of creating something new. It is very powerful. Armed with this knowledge, you are ready to move on and implement the core logic of the nightly report job.

Retrieving the User’s Todos

The next step is to leverage Spring’s auto-wired dependency injection to inject the BatchService into the job, as follows:

> grails create-service Batch

Listing 11-3 illustrates injection and execution of the BatchService.

Listing 11-3.  NightlyReportJob with Batch Service

class NightlyReportJob {
  def cronExpression = "0 0 1 * * *" // Run every day at 1:00 a.m.
  def name = "Nightly"               // Job name
  def group = "CollabTodo"           // Job group

//  def startDelay = 20000           // Wait 20 seconds to start the job
//  def timeout = 60000              // Execute job once every 60 seconds

  def batchService
  def execute() {
      log.info "Starting Nightly Job: "+new Date()
      batchService.nightlyReports.call()
      log.info "Finished Nightly Job: "+new Date()
  }
}

The code is straightforward. It defines when the job is to run and delegate to the BatchService.

The next step is to create the nightly closure on the batch service. It will contain the code to retrieve the user’s todos. Listing 11-4 illustrates adding the nightly closure and retrieving the user’s todos.

Listing 11-4.  Batch Service Nightly Closure

class BatchService

  . . .

  /*
  *  Runs nightly reports
  */
  def nightlyReports = {
      log.info "Running Nightly Reports Batch Job: "+new Date()
      // 1. Gather user w/ email addresses.
      def users = User.withCriteria {
               isNotNull('email')
      }

      users?.each { user ->
          // 2. Invoke report service for each user.
          //    Can't reuse ReportController because it makes too
          //    many assumptions, such as access to session.class.
          //
          //    Reuse Report Service and pass appropriate params.
          // Gather the data to be reported.
          def inputCollection = Todo.findAllByOwner(user)

          // To be completed in the next section

      }

      log.info "Completed Nightly Reports Batch Job:  "+new Date()
  }

The BatchService.nightlyReports gets all users with an e-mail address, and then for each user, gets their todos and prepares to invoke the report service.

Invoking the Report Service

In Chapter 10, you used JasperReports to build a report facility. You can reuse components of the report facility to create a todo report PDF to attach to the e-mail.

Your first thought might be to use the ReportController. Well, that doesn’t work. The report controller is dependent on the HTTP session and renders the PDF to the output stream. You need to go one level deeper and use the ReportService directly.

We have already retrieved the user’s todos. Now all we need to do is pass the todos, report template, and a username parameter to the report service. The highlighted section of Listing 11-5 illustrates the required steps.

Listing 11-5.  Invokingthe Report Service

class BatchService {
ReportServicereportService  // Inject ReportService

  def nightlyReports = {

. . .

      users?.each { user ->
          // 2. Invoke Report Service for each user.
          //    Reuse Report Service and pass appropriate params.
          // Gather the data to be reported.
          def inputCollection = Todo.findAllByOwner(user)
          Map params = new HashMap()
          params.inputCollection = inputCollection
          params.userName = user.firstName+""+user.lastName

          // Load the report file.
          def reportFile = this.class.getClassLoader().getResource(
           "web-app/reports/userTodo.jasper")
          ByteArrayOutputStream byteArray = reportService.generateReport(reportFile,
          reportService.PDF_FORMAT,params )
          Map attachments = new HashMap()
          attachments.put("TodoReport.pdf", byteArray.toByteArray())

          // 3. Email results to the user.
          sendNotificationEmail(user, attachments)

      }
  }

The new code works as follows:

  1. Injects the ReportService into the BatchService.
  2. Creates a HashMap of parameters that will be passed to the ReportService. The parameters include the list of todos for the current user.
  3. Loads the JasperReports template from the classpath.
  4. Invokes reportService.generateReport to pass the report template, report format (PDF), and parameters.

Now that you have a PDF report, the next step is to e-mail it to the user.

Invoking the E-Mail Service

In Chapter 8, you implemented an SMTP e-mail service, called EMailAuthenticatedService. You can use your e-mail service to send the todo report to the user. Listing 11-6 contains the code required to create and send the e-mail.

Listing 11-6.  Sending the E-mail

1.  class BatchServiceimplements ApplicationContextAware{
2.    boolean transactional = false
3.
4.  public void setApplicationContext(ApplicationContext applicationContext) {
5.    this.applicationContext = applicationContext
6.  }
7.  def ApplicationContext applicationContext
8.  def EMailAuthenticatedService EMailAuthenticatedService  // injected
9.
10. ReportService reportService
11.
12. def nightlyReports = {
13.
14. ...
15.
16.       // Load the report file
17.       def reportFile = this.class.getClassLoader().getResource(
18.           "web-app/reports/userTodo.jasper")
19.       ByteArrayOutputStream byteArray =
20.           reportService.generateReport(reportFile,
21.           reportService.PDF_FORMAT,params )
22.
23.       Map attachments = new HashMap()
24.       attachments.put("TodoReport.pdf", byteArray.toByteArray())
25.
26.       // 3. Email results to the user.
27.       sendNotificationEmail(user, attachments)
28.       }
29.       log.info "Completed Nightly Batch Job:  "+new Date()
30.  }
31.
32. def private sendNotificationEmail = {User user, Map attachments ->
33.   def emailTpl = this.class.getClassLoader().getResource(
34.       "web-app/WEB-INF/nightlyReportsEmail.gtpl")
35.   def binding = ["user": user]
36.   def engine = new SimpleTemplateEngine()
37.   def template = engine.createTemplate(emailTpl).make(binding)
38.   def body = template.toString()
39.   def email = [
40.           to: [user.email],
41.     subject: "Your Collab­Todo Report",
42.     text:   body
43.   ]
44.   try {
45.     EMailProperties eMailProperties =
46.           applicationContext.getBean("eMailProperties")
47.     eMailAuthenticatedService.sendEmail(email, eMailProperties, attachments)
48.   } catch (MailException ex) {
49.     log.error("Failed to send emails", ex)
50.   }
51.  }
52. }

The highlighted lines contain the changes made to the batch service. Lines 1 and 4-7 make the batch service (Spring) application context-aware; in other words, the Spring application context is injected into the service. You will use the application context later to look up some information. Line 8 takes advantage of Spring auto-wiring to inject the EmailAuthenticatedService. Lines 23 and 24 add the PDF report to a map of attachments for e-mail. Line 27 invokes a local sendNotificationEmail closure.

Lines 32-51 contain the code to send the todo report e-mail to the user. Line 33 loads an e-mail template. Lines 36-38 use the Groovy SimpleTemplateEngine 2 to generate the e-mail body. Lines 39-43 define a map of e-mail parameters that are passed to the e-mail service. Line 45 uses the Spring application context to look up e-mail properties, including the “from” address. Line 47 invokes the e-mail service, sending the e-mail map, e-mail properties, and the attachments.

Summary

This chapter demonstrated Grails’ ability to reuse open source, third-party Java libraries. You installed the Quartz plugin, created a simple job, and saw how to control the frequency of execution using the timeout property.

Next, you started to build the batch­reporting facility. You created a NightlyReportJob and configured it to run at 1:00 a.m. using the cronExpression property. You learned that cron expressions are robust and provide fine-grained control over when the NightlyReportJob is invoked.

The NightlyReportJob delegated to a batch service that was injected using Spring auto-wiring and invoked nightlyReports. nightlyReports iterated through a list of users, gathered their todos, invoked the report service built in Chapter 10 to generate a PDF attachment, and e-mailed the attachment to the user using the EmailAuthenticatedService built in Chapter 8.

1 http://www.quartz-scheduler.org/

2 http://groovy.codehaus.org/Groovy +Templates

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset