пятница, 29 ноября 2013 г.

WebSphere Portal 7. Longrunning tasks in portlets.

Sometimes operations in portlets can take a long time to complete, like a report generating task. It is better not to execute such tasks in a synchronous way, because :
  • they block the web container thread, which slows down user satisfaction;
  • make the appearance of application to freeze;
  • the task can take up time of no more then request timeout;
  • you cannot cancel the task;
  • you don't see the progress of your task.


You'd better do such operations in an asynchronous way in a separate thread. But it is prohibited to spawn your own threads. Among the reasons against this are:
  • custom threads are not managed by application server;
  • J2EE context is not propagated to custom managed threads. That means that you cannot get JNDI resources.
To rescue WebSphere work manager comes. A work manager is a thread pool created for Java Platform, Enterprise Edition (Java EE) applications that use asynchronous beans.

Here is the solution to execute the tasks:
  • use AJAX to create and start an asynchronous task, save it in portlet session
  • use AJAX to show to user the progress of task completion
  • after completion of task get the results.
For AJAX solutions it is better to use "serveResource" method, but if you use spring portlet you should use @ResourceMapping annotation. I will use as an example Spring portlet MVC 3. Moreover spring framework contains convenient wrapper against work manager api, so you don't have to study new proprietary api. This wrapper is org.springframework.scheduling.commonj.WorkManagerTaskExecutor class, which implements  AsyncTaskExecutor interface. So, all you have to do is implement the Runnable interface in you class an then submit the task to WorkManagerTaskExecutor. This also means that during your testing you can substitute one task executor with any other available and your code will continue to work.

At first, you should define the work manager in WebSphere Application Server. You can look at the setup here.Take a look at parameter "Work request queue full action". Set it to FAIL, or the execute method will block.

In web.xml declare the pool:
  <resource-ref>
     <description>WorkManager</description>
     <res-ref-name>wm/reportWorker</res-ref-name>
     <res-type>commonj.work.WorkManager</res-type>
     <res-auth>Container</res-auth>
     <res-sharing-scope>Shareable</res-sharing-scope>
 </resource-ref>

In ibm-web-bnd.xml declare:
 <?xml version="1.0" encoding="UTF-8"?>
<web-bnd
    xmlns="http://websphere.ibm.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://websphere.ibm.com/xml/ns/javaee http://websphere.ibm.com/xml/ns/javaee/ibm-web-bnd_1_0.xsd"  version="1.0">
    <virtual-host name="default_host" />
    <resource-ref name="wm/reportWorker" binding-name="wm/reportWorker" />
</web-bnd>

In applicationContext declare work manager:
<!--  workManager -->
<bean id="wmTaskExecutor" class="org.springframework.scheduling.commonj.WorkManagerTaskExecutor">
       <property name="workManagerName" value="wm/reportWorker" />
      <property name="resourceRef" value="true" />
</bean>

In controller define the work manager:
 @Autowired
 @Qualifier("wmTaskExecutor")
 private AsyncTaskExecutor executor;

In resouremapping method like this:
 @ResourceMapping(value = "exportSearchResultsAsXlsx")
public void exportSearchResultsAsXlsx(ResourceRequest request, ResourceResponse response, PortletSession porsession)

create you task, submit it to executor and store the reference to it in portlet session:
ExportDocResultThread exportDocResultThread = new ExportDocResultThread();
executor.execute(exportDocResultThread, AsyncTaskExecutor.TIMEOUT_IMMEDIATE);
porsession.setAttribute(EXPORT_DOC_RESULT_THREAD, exportDocResultThread);

After this you can query your portlet session for a thread and ask it for status, get results or cancel.
ExportDocResultThread  has the following signature:
public class ExportDocResultThread implements java.lang.Runnable.


Unit testing such tasks is easy. You create executor and send task to it:
TaskExecutor executor = new ConcurrentTaskExecutor();
executor.execute(exportDocResultThread);

Also, note that execute method can throw TaskRejectedException if thread waiting queue executor is full.

Комментариев нет:

Отправить комментарий