Example batch process for unit of work processing
The following Gosu code is an example of a type of a batch process that processes units of work rather than operating as a background task. Its process history tracks the number of items that were processed successfully or that failed. Its units of work are urgent activities.
The batch process implements a notification scheme for urgent activities such that:
- The activity owner must respond to an urgent activity within 30 minutes.
- If the activity owner does not handle the issue within that time limit, the batch process notifies a supervisor.
- If the supervisor fails to resolve the issue in 60 minutes, the batch process sends a message further up the supervisor chain.
If there are no qualified activities, the batch process returns false so that it does not create a
process history. If there are items to handle, the batch process increments the count. The application uses this
count to display batch process status as "n of t" or a progress bar. If there are no contact
email addresses, the task fails and the application flags it as a failure.
This example checks the value of the TerminateRequested flag to terminate
the loop if the user or the application requested to terminate the process.
Notice also, that if the a TestBatch typecode on the BatchProcessType typelist does
not exist, you must create it.
The code in this Gosu example does not actually send the email. Instead, the code prints the email information to the console. You can change the code to use real email APIs.
package sample.processes
uses gw.api.database.Query
uses gw.api.email.Email
uses gw.api.email.EmailContact
uses gw.api.email.EmailUtil
uses gw.api.profiler.Profiler
uses gw.api.profiler.ProfilerTag
uses gw.api.util.DateUtil
uses gw.processes.BatchProcessBase
uses java.lang.StringBuilder
uses java.util.HashMap
uses java.util.Map
class TestBatch extends BatchProcessBase {
static var tag = new gw.api.profiler.ProfilerTag("TestBatchTag1")
//If BatchProcessType typcode TestBatch does not exist, you must create it.
construct() {
super( BatchProcessType.TC_TESTBATCH )
}
override function requestTermination() : boolean {
super.requestTermination() // set the TerminationRequested flag
return true // return true to signal that we will attempt to terminate in our doWork method
}
override function doWork() : void { // no bundle
var frame = Profiler.push(tag)
try {
var activityQuery = Query.make(Activity)
activityQuery.compare(Activity#Priority, Equals, Priority.TC_URGENT)
activityQuery.compare(Activity#Status, Equals, ActivityStatus.TC_OPEN)
activityQuery.compare(Activity#CreateTime, LessThan, DateUtil.currentDate().addMinutes(-30))
OperationsExpected = activityQuery.select().getCount()
var map = new HashMap<Contact, StringBuilder>()
for (activity in activityQuery.select()) {
if (TerminateRequested) {
return
}
incrementOperationsCompleted()
var haveContact = false
var msgFragment = constructFragment(activity)
haveContact = addFragmentToUser(map, activity.AssignedUser, msgFragment) or haveContact
var group = activity.AssignedGroup
if (activity.CreateTime < DateUtil.currentDate().addMinutes( -60 )) {
while (group != null) {
group = group.Parent
haveContact = addFragmentToUser(map, group.Supervisor, msgFragment) or haveContact
}
}
if (!haveContact) {
incrementOperationsFailed()
addFragmentToUser(map, User.util.UnrestrictedUser, msgFragment)
}
}
if (not TerminateRequested) {
for (addressee in map.Keys) {
sendMail(addressee, "Urgent activities still open", map.get(addressee).toString())
}
} finally {
Profiler.pop(frame)
}
}
private function constructFragment(activity : Activity) : String {
return formatAsURL(activity) + "\n\t" + " Subject: " + activity.Subject + " AssignedTo: "
+ activity.AssignedUser + " Group: " + activity.AssignedGroup + " Supervisor: "
+ activity.AssignedGroup.Supervisor + "\n\t" + activity.Description
}
private function formatAsURL(activity : Activity) : String {
// TODO: YOU MUST ADD A PCF ENTRYPOINT THAT CORRESPONDS TO THIS URL TO DISPLAY THE ACTIVITY.
return "http://PolicyCenter:8180/pc/Activity.go(${activity.ID})
}
private function addFragmentToUser(map : HashMap<Contact, StringBuilder>, user : User,
msgFragment : String) : boolean {
if (user != null) {
var email = user.Contact.EmailAddress1
if (email != null and email.trim().length > 0) {
var sb = map.get(email)
if (sb == null) {
sb = new StringBuilder()
map.put(user.Contact, sb)
}
sb.append(msgFragment)
return true
}
}
return false
}
private function sendMail(contact : Contact, subject : String, body : String) {
var email = new Email()
email.Subject = subject
email.Body = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" +
"<html>\n" +
" <head>\n" +
" <meta http-equiv=\"content-type\"\n" + "content=\"text/html; charset=UTF-8\">\n" +
" <title>${subject}</title>\n" +
" </head>\n" +
" <body>\n" +
" <table>\n" +
" <tr><th>Subject</th><th>User</th><th>Group</td><th>Supervisor</th></tr>" +
" </table>" +
" </body>" +
"</html>"
email.addToRecipient(new EmailContact(contact))
EmailUtil.sendEmailWithBody(null, email);
}
}
To use this entry point, create the following PCF entry point file with name Activity.
<PCF>
<EntryPoint authenticationRequired="true" id="Activity" location="ActivityForward(actvtIdNum)">
<EntryPointParameter locationParam="actvtIdNum" type="int"/>
</EntryPoint>
</PCF>
In Studio, create this Activity.pcf file in the following location:
Also include the following ActivityForward PCF file as the ActivityForward.pcf
in each place in the PCF hierarchy that you need it:
<PCF>
<Forward id="ActivityForward">
<LocationEntryPoint signature="ActivityForward(actvtIdNum : int)"/>
<Variable name="actvtIdNum" type="int"/>
<Variable name="actvt" type="Activity">initialValue="find(a in Activity
where a.ID == new Key(Activity, actvtIdNum))"</>
<ForwardCondition action="PolicyForward.go(actvt.Policys);
ActivityDetailWorksheet.goInWorkspace(actvt)"/>
</Forward>
</PCF>
runWithNewBundle transaction method to modify entity data in
your custom batch process. For more information, see Running code in an entirely new bundle.