Showing posts with label Web Service. Show all posts
Showing posts with label Web Service. Show all posts

Friday, May 1, 2009

Java Multithreaded Web Service Performance Tips

Right after Java 5.0 Concurrency Model was released, MultiThreaded Java Web Service application is always an interesting subject. It's easy to tweak codes that are slowing down the application. But it was always a challenge to tweak it at the container level.

A former colleague of mine and me worked to figure out why our Web Service Application was slowing down and always leaves lots of run-away processes. Here are some of the findings we had implemented to keep our application rolling! The sample codes below reflects a reporting process where requests to generate a report is made and how the threads handle the status of each requests.

Use Java Concurrency API
There are only three classes that I've used to implement a MultiThreaded WS Application.
Runnable, ScheduledExecutorService, and ConcurrentLinkedQueue. 

The ScheduledExecutorService implements ExecutorService and is of type Executor.  Executor is responsible for abstracting the gory details of implementing threads into simpler methods. And it works all the time.

ScheduledExecutorService allows you to provide what the initial time is to run the thread associated with it, at what frequency, the thread to run and the unit of time. Was very easy to use and you can configure the thread pool size as well. The application I wrote had 5 threads running concurrently and it never had any problems at all. They all shared the ConcurrentLinkedQueue object resource and I didn't use the synchronized keyword to make this object thread safe since by default it already is.


Using the Runnable interface is better than just extending the Thread class. It promotes less coupling. Here's a sample of how this thread is plugged-in to the ScheduledExecutorService.



Implement the ServiceLifeCycle
Implementing ServiceLifeCycle will allow you to put shutdown hooks and implement its destroy() method where you can manipulate how the spawned threads are to be shutdown/killed. The init(Object context) method must also be implemented.



Provide Shutdown Hooks
The shutDownMonitor() method is called and the ExecutorService's awaitTermination() method is invoked which basically gracefully shuts down the spawned thread in the WS Container according to the specified time (might be in seconds, minutes, hours). You can also force to shutdown all threads by calling the method shutdownNow() if it takes longer than the specified time.


Use "application" Scoping
When you use "request" scope as configured in your wsdd file, you are creating one instane of the service per request. If several requests were triggered, your application is also spawning threads per request. The previous request that was processed left a trail of run-away processes. If the scope was "application" it will create only one instance of the service that will serve multiple requests. Hence, when the application runs it does not create run away processes since we already have a shutdown hook to kill all of the spawned threads before the application completely shutdown.


I think it all depends on what an application is trying to achieve, it might have more complex business process than this sample code we have and it requires more classes to use from the concurrent api. But in a multithreaded web service application, you definitely want to use the application scope and implement a shutdown hook.

Monday, April 13, 2009

Using Rally Java Web Service API

 There are a number of developer tools that can be used from Rally ranging from integrating Rally to a different application(s) or just plainly extracting data from Rally. This link https://rally1-wiki.rallydev.com/display/Word/Developer+Tools provides necessary rally developer documentation.

Web Service API

Since Rally supports several implementation of their WS API this will mainly focus on SOAP in Java. You can find on this link https://rally1.rallydev.com/slm/doc/webservice/ the other implementations. The only thing I don't like about this API implementation is that you always have to pass the object reference through the wire to get the values of the object. The WS calls are so fine-grained that the number of objects queried is directly proportional to the number of round-trip calls. The sample usage of the SOAP in Java implementation is shown below in sequence.

Assuming our target of interest is to extract a Story from Rally. The Story in Rally is actually map to a SOAP object called HierarchicalRequirement. Always remember to use the read() method of RallyService? object to grab the physical object from Rally.

  1. Grab the WSDL from the current version of your Rally application which might have the form https://rallyx.rallydev.com/slm/webservice/x.xx/meta/34343483/rally.wsdl.
  2. Generate the Java code from the given wsdl file. Their will be 3 packages generated - com.rallydev.webservice.domain and com.rallydev.webservice.service.
    • com.rallydev.webservice.domain contains all SOAP objects that represent the data in Rally.
    • com.rallydev.webservice.service contains the web service interface.
  3.  Acquire connection from the web service endpoint and grab available Workspaces.
     URL url = new URL("https://rally1.rallydev.com/slm/webservice/1.10/RallyService");
    RallyService service = (new RallyServiceServiceLocator()).getRallyService(url);

    Stub stub = (Stub)service;
    stub.setUsername(rally_username);
    stub.setPassword(rally_password);
    stub.setMaintainSession(true);
    Subscription subscription = (Subscription)service.getCurrentSubscription();
    Workspace[] workspaces = subscription.getWorkspaces();
    if(workspaces==null || workspaces.length==0){
    errorBuf.append("The login credentials doesn't have any subscription or there are " +
    "no Workspaces configured from Rally.");
    writeToFile(serviceBean, errorBuf.toString());
    return null;
    }
  4. If the target workspace is "IT: the next generation" then loop through the workspaces that matches that workspace.
    Workspace workspace = null;
    for(int i=0; i<workspaces.length;i++){
    WSObject wsObject = (WSObject)service.read(workspaces[i]);
    workspace = (Workspace)wsObject;
    String workspaceName = workspace.getName();
    if(workspaceName.equalsIgnoreCase("IT: the next generation" )){
    break;
    }
    }
  5. Submit query and get results (DomainObject?[]). The serviceBean.getQuery() is a name/value pair which might be of the form Release.Name= "Test Release For TWiki" AND ScheduleState? = "Completed". Process each DomainObject?.
    QueryResult queryResult = service.query(workspace, "HierarchicalRequirement", serviceBean.getQuery(), "", false, 1, 100);
    if(queryResult.getErrors().length>0){
    for(int i=0; i<queryResult.getErrors().length;i++){
    errorBuf.append("ERROR: ");
    errorBuf.append(queryResult.getErrors()[i]);
    errorBuf.append("\n");
    }
    writeToFile(serviceBean, errorBuf.toString());
    return null;
    }
    DomainObject[] domainObjects = queryResult.getResults();
    if(domainObjects!=null && domainObjects.length>0){
    List releaseNotesBeanList = new ArrayList();
    TwikiBean twikiBean = new TwikiBean();
    Map packageStoryMap = new HashMap();
       for(int i=0;i<domainObjects.length;i++){
    HierarchicalRequirement story = (HierarchicalRequirement)service.read(domainObjects[i]);
    Release release = (Release)service.read(story.getRelease());
    DateFormat dateFormat = DateFormat.getInstance();
    String releaseDate = dateFormat.format(release.getReleaseDate().getTime());
          if(story.getAttachments()==null || story.getAttachments().length==0){
    errorBuf.append(NO_ATTACHMENT).append("\n");
    }
    if(story.get_package()==null || story.get_package().equals("")){
    errorBuf.append(NO_PACKAGE_NAME).append("\n");
    }
    if(release==null || release.getName()==null
    || release.getName().equals("")){
    errorBuf.append(NO_RELEASE_NAME).append("\n");
    }
          if(errorBuf.length()>0){
    errorBuf.insert(0, "ERROR: User Story ID: " + story.getFormattedID() + "\n");
    writeToFile(serviceBean, errorBuf.toString());
    continue;
    }
          ReleaseNotesBean releaseNotesBean = new ReleaseNotesBean();
    releaseNotesBean.setPackageName(story.get_package());
    releaseNotesBean.setUserStoryId(story.getFormattedID());
    releaseNotesBean.setUserStoryName(story.getName());
    releaseNotesBean.setReleaseDate(releaseDate);
    releaseNotesBean.setReleaseName(release.getName());
          Attachment[] attachments = story.getAttachments();
    Attachment attachment = attachments[0];
    attachment = (Attachment)service.read(attachment);
          AttachmentContent attachmentContent = (AttachmentContent)service.read(attachment.getContent());
    byte[] content = attachmentContent.getContent();
          String twikiTopic = new String(content);
    releaseNotesBean.setTwikiTopic(twikiTopic);
          releaseNotesBeanList.add(releaseNotesBean);
    prepareTopics(releaseNotesBean, packageStoryMap);
    }
       twikiBean.setPackageStoryMap(packageStoryMap);
    twikiBean.setReleaseNotesBeanList(releaseNotesBeanList);
    return twikiBean;
    }