Wednesday, May 2, 2012

Java Executor with Spring xml configuration - A Sample

Consider a simple flow - I need to generate a report by aggregating together the different sections of a report.


A client call, say, is along these lines, for a report with 5 different sections:

ReportRequestPart part1 = new ReportRequestPart(Section.HEADER, context);
ReportRequestPart part2 = new ReportRequestPart(Section.SECTION1, context);
ReportRequestPart part3 = new ReportRequestPart(Section.SECTION2, context);
ReportRequestPart part4 = new ReportRequestPart(Section.SECTION3, context);
ReportRequestPart part5 = new ReportRequestPart(Section.FOOTER, context);

requestParts.add(part1); 
requestParts.add(part2);
requestParts.add(part3);
requestParts.add(part4);
requestParts.add(part5);
ReportRequest reportRequest  = new ReportRequest(requestParts );
reportGenerator.generateReport(reportRequest);

Here ReportGenerator is the service class responsible for generating the report given the sections of the report.

The part of the report for each section can be generated independently, so a java based concurrent implementation would start by defining a task - which is done by implementing a Runnable or Callable interface. Callable interface is implemented for tasks that return a result, like in this case where I want to return a part of a report from a task and put the different parts together into a report.

This is how my Report part task looks:

public class ReportPartRequestCallable implements Callable<ReportPart> {
 private final ReportRequestPart reportRequestPart;
 private final ReportPartGenerator reportPartGenerator;

 public ReportPartRequestCallable(ReportRequestPart reportRequestPart, ReportPartGenerator reportPartGenerator) {
     this.reportRequestPart = reportRequestPart;
     this.reportPartGenerator = reportPartGenerator;
    }

    @Override
    public ReportPart call() {
    return this.reportPartGenerator.generateReportPart(reportRequestPart);
    } 
}

Now given this task my report generator using Java Executor would start by creating a threadpool:
private ExecutorService executors = Executors.newFixedThreadPool(10);

In this instance, I am using the Executors utility class to create a fixed sized thread pool.
The next job is to simply submit the individual parts of the report request to this thread pool and collect back the results:
@Override
public Report generateReport(ReportRequest reportRequest) {
 List<Callable<ReportPart>> tasks = new ArrayList<Callable<ReportPart>>();
 List<ReportRequestPart> reportRequestParts = reportRequest.getRequestParts();
 for (ReportRequestPart reportRequestPart : reportRequestParts) {
  tasks.add(new ReportPartRequestCallable(reportRequestPart, reportPartGenerator));
 }

 List<Future<ReportPart>> responseForReportPartList;
 List<ReportPart> reportParts = new ArrayList<ReportPart>();
 try {
  responseForReportPartList = executors.invokeAll(tasks);
  for (Future<ReportPart> reportPartFuture : responseForReportPartList) {
   reportParts.add(reportPartFuture.get());
  }

 } catch (Exception e) {
  logger.error(e.getMessage(), e);
  throw new RuntimeException(e);
 }
 return new Report(reportParts);
}

Here I am using the invokeAll method to kick off the tasks and then using Future.get to join the result together.

Springs abstraction to java Executor is the TaskExecutor, TaskExecutor however cannot be used for executing tasks which return a result(Callable), it can only execute Runnable tasks, which don't return result. So if I want to inject in a Executor to my report generator, this is how I would go about it:
<bean name="taskExecutorBasedReportGenerator" class="org.bk.sisample.taskexecutor.TaskExecutorsBasedReportGenerator">
     <property name="executors">
      <bean class="java.util.concurrent.Executors" factory-method="newFixedThreadPool">
       <constructor-arg value="10"></constructor-arg>
      </bean>
     </property>
     <property name="reportPartGenerator" ref="reportPartGenerator"/>
    </bean>

No comments:

Post a Comment