Parallel task execution in J2EE using the Work Manager specification
Pages: 1, 2, 3, 4

Sequential implementation

To keep things simple we create an AbstractServlet first, which encapsulates all HttpServlet-related code and delegates the actual translation work to child classes (Strategy pattern).

public abstract class AbstractServlet extends HttpServlet {
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
       throws IOException, ServletException  {
       logger.info("begin");
                
        response.setContentType("text/plain");
        PrintWriter writer = resp.getWriter();

        long start = System.currentTimeMillis();

        List input = getClientInput();
        List result = null;
        try {
             
                         result = doTranslation(input);
        } 
        catch (Exception e) {
            throw new ServletException(e);
        }

        long stop = System.currentTimeMillis();
        logger.info("done in "+(stop-start));
        writer.print("done in "+(stop-start)+"\n"+result);
    }
        
    // actual translation logic goes here
     
                         protected abstract List doTranslation(List input) throws Exception;

    // let's hardcode list for our test
    // in real life this method should extract parameters from HttpServletRequest
    private List getClientInput() {
        return Arrays.asList(new String[]{"one", "two", "three", "four", "five"});
    }
}
                      

And finally, here is the code for sequential execution:

public class ServletSequential extends AbstractServlet {
    private static final long DELAY = 10 * 1000; // 10 sec

    protected List doTranslation(List input) {
        List result = new ArrayList();
        for (Iterator iter = input.iterator(); iter.hasNext();) {
            String source = (String) iter.next();
            Translator translator = new DummyTranslator(source, DELAY);
            translator.translate();
            result.add(translator.getTranslation());
        }
        return result;
    }
}

When this servlet is executed, it will take 50 seconds to "translate" the five words we're using as a parameter (each invocation of DummyTranslator.translate() takes 10 seconds). Let's now take a look at how this example could be rewritten to take advantage of WorkManager functionality.

Modifying the code to execute in parallel

To modify the code to take advantage of parallel execution, the first step is to modify Translator to implement the Work interface. There are two ways to do this: Either we could modify AbstractTranslator to implement Work in addition to the Translator interface, or we could provide some sort of wrapper class that can take a Translator instance and implement Work, proxying method calls. We'll take the second approach, which allows us to reuse the existing DummyTranslator without modification.

public class WorkTranslatorWrapper implements Translator, Work {
    private final Translator impl;

    public WorkTranslatorWrapper(Translator impl) {
        this.impl = impl;
    }
        
    public String getSource() {
        return this.impl.getSource();
    }

    public String getTranslation() {
        return this.impl.getTranslation();
    }

    public void translate() {
        this.impl.translate();
    }

    public void release() {
    }

    public boolean isDaemon() {
        return false;
    }

    public void run() {
        translate();
    }

    public String toString() {
        return this.impl.toString();
    }

As you can see, WorkTranslatorWrapper provides implementations for all methods defined in the Translator interface, proxying all of them to the concrete Translator implementation passed as a constructor parameter. Three new methods are defined to satisfy the Work interface (which, in turn, extends Runnable). The run() method is an entry point to Work execution; in our case we're simply redirecting it to translate(). The release() method can be used to set any variables to terminate the main loop in run() (if any) and return pretty much in the same way as recommended for Java threads. The isDaemon() method should return true if Work can outlive the servlet request or EJB method that scheduled it. If false is returned, Work normally should not last longer than the submitting container method. The submitting method should wait until the short-lived (non-daemon) work is complete if using resources that are only valid during the method's duration. For our example, non-daemon is the right way to go since we want to wait until all results are available before returning the result to the client.

Now that we have our Work implemented we're ready to rewrite the servlet code to use WorkManager, but first we need to define WorkManager in our container.

Defining WorkManager in Container

Work Managers are container resources, just like JMS queues and connection pools. You can configure these components using the Administration Console. In essence, a WorkManager is just a thread pool with capacity parameters, with the minimum and maximum number of threads to allocate, response time strategies, and so on. None of these options are modifiable through the console in the beta version though, so the whole configuration comes down to providing a name for the newly created WorkManager. We use MyWorkManager for this example.

Figure 1
Figure 1. WorkManager configuration in WebLogic 9.0

When you've completed the configuration, restart WebLogic Server; on startup, you should see (either in the console or in log files, depending on your logging settings) that MyWorkManager is created for your application:

<Mar 31, 2005 5:42:47 PM EST> <WorkManager> \
   <Creating WorkManager from "MyWorkManager" \
   WorkManagerMBean for application "workman.war">

Now the MyWorkManager you just defined is available in the JNDI tree. The container can support an arbitrary number of independent WorkManager instances. The primary method for obtaining a WorkManager instance is through a JNDI lookup in the local Java environment (that is, java:comp/env/wm/[work manager name]). Therefore, Work Managers are configured at deployment time through deployment descriptors as resource-refs. Each JNDI lookup of a specific Work Manager (for example, wm/MyWorkManager) returns a shared instance of that WorkManager. Since WorkManager is thread-safe, the lookup could be done once, usually in init() method of a servlet or ejbCreate() method of a session EJB.

The recommended configuration method is to define the resource-ref in the corresponding standard deployment descriptor, web.xml (or ejb-jar.xml for EJBs):

 <web-app>
 ...
  <resource-ref>
    <res-ref-name>wm/MyWorkManager</res-ref-name>
    <res-type>commonj.work.WorkManager</res-type>
    <res-auth>Container</res-auth>
    <res-sharing-scope>Shareable</res-sharing-scope>
  </resource-ref>
  ...
 </web-app>

as well as in WebLogic-specific deployment descriptors weblogic.xml (or weblogic-ejb-jar.xml):

  <weblogic-web-app>
  ...
  <reference-descriptor>
    <resource-description>
      <res-ref-name>wm/MyWorkManager</res-ref-name>
      <jndi-name>MyWorkManager</jndi-name>    
    </resource-description>
  </reference-descriptor>
  ...
  </weblogic-web-app>

Pages: 1, 2, 3, 4

Next Page »