Tuesday, April 12, 2016

Annotated serveResource in JSR 286 portlet


I wonder why serveResource method was left out while providing annotated methods in JSR 286.
It is a frequent requirement now-a-days to implement multiple AJAX calls while developing JSR 286 based portlets.
There are many different design patterns through which this can be achieved, but still thought of giving a try to annotate serveResource method.

What I did was creating a base portlet class which will be extended by the other portlet classes, but this is not required and can be directly done in the requisite portlet class.

Firstly, created an annotation interface ResourceMapping, this defines the annotation of @ResourceMapping with name as a variable.



package in.blogspot.wpwizard.portlets;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface ResourceMapping {
 String name();
}

Then in BasePortlet class added a private method to cache the annotated resource methods during the portlet initialization.
Then in the overridden serveResource method checked value of parameter javax.portlet.serveResource which should denote the name of the annotated methods name parameter.



package in.blogspot.wpwizard.portlets;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;



public abstract class BasePortlet extends GenericPortlet {
 private transient Map<String, Method> serveResourceHandlingMethodsMap = new HashMap<String, Method>();


 @Override
 public void init() throws PortletException {
  super.init();

  try {
   cacheAnnotatedResourceMethods(); //cache annotated resource serving methods on init
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 @Override
 public void destroy() {
  super.destroy();
 }

 /**
  * In order to annotate the serveResource use <code>@ResourceMapping</code> 
  * and provide the variable name of parameter <code>javax.portlet.resource</code> in <code>resourceURL</code> 
  * 
  */
 public void serveResource(ResourceRequest request, ResourceResponse response)
   throws PortletException, IOException {
  try {
   String resource = request.getParameter("javax.portlet.serveResource");
   Method resourceMethod = serveResourceHandlingMethodsMap.get(resource);
   if (resourceMethod != null) {
    resourceMethod.invoke(this, request, response);
    return;
   }
  } catch (Exception e) {
   throw new PortletException(e);
  }

 }


 @Override
 protected void doView(RenderRequest request, RenderResponse response)
   throws PortletException, IOException {

 }


 private void cacheAnnotatedResourceMethods() {
  // cache all annotated resource serving methods
  for (Method method : this.getClass().getMethods()) {
   Annotation[] annotations = method.getAnnotations();
   if (annotations != null) {
    for (Annotation annotation : annotations) {
     Class<? extends Annotation> annotationType = annotation.annotationType();
     if (ResourceMapping.class.equals(annotationType)) {
      String name = ((ResourceMapping) annotation).name();
      if (name != null && name.length() > 0)
       serveResourceHandlingMethodsMap.put(name, method);
     } 
    }
   }
  }
 }

}

In the individual portlet class of MyPortlet added the annotated methods for serving resource.



package in.blogspot.wpwizard.portlets;

import java.io.IOException;
import java.io.PrintWriter;

import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;


public class MyPortlet extends BasePortlet {
  
 public void init() throws PortletException{
  super.init();
 }

 public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {
  
  response.setContentType(request.getResponseContentType());
  PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher("/jsp/view.jsp");
  rd.include(request,response);
 }

 @ResourceMapping(name="getCountry")
 public void getCountry(ResourceRequest request, ResourceResponse response)throws PortletException, IOException{
  System.out.println("entered getCountry");
  PrintWriter out = response.getWriter();
  out.print("return country list");
  
 }
 
 @ResourceMapping(name="getState")
 public void getState(ResourceRequest request, ResourceResponse response)throws PortletException, IOException{
  System.out.println("entered getState");
  PrintWriter out = response.getWriter();
  out.print("return state list");
  
 }

}

In the JSP added the following tags to form an URL to these annotated method. The value passed in the param with name javax.portlet.serveResource should match the annotation name variable of the method. (As we do in the processAction annotation)



<portlet:resourceURL var="getCountryURL">
 <portlet:param name="javax.portlet.serveResource" value="getCountry" />
</portlet:resourceURL>
<portlet:resourceURL var="getStateURL">
 <portlet:param name="javax.portlet.serveResource" value="getState" />
</portlet:resourceURL>

Voila it works !!


1 comment: