Monday, 12 December 2011

Retrieving URL parameters from JSR 168 portlets using WebSphere services

The general approach you see in this article is:
  1. Intercept the user's request with a servlet filter.
  2. In the servlet filter, grab the parameter and store it temporarily in a dynamic cache.
  3. In the portlet, grab the parameter from the dynamic cache.
That's the "50,000 foot" block-diagram version of the approach. Let's see how to implement it.
You start by creating the cache. Stefan Hepper and Stephan Hesmer talk about using a dynamic cache in portlets in the developerWorks article titled "Caching data in JSR 168 portlets with WebSphere Portal V5.1". (See the http://www.ibm.com/developerworks/websphere/library/techarticles/0707_lynn/0707_lynn.html#resources for a link. In the article, see the section titled Leveraging the WebSphere dynacache infrastructure.) They offer a couple of different ways to create and set up the dynacache. The dynamic cache infrastructure is pretty nifty and, if you have a few moments, I'd suggest taking a look at it for any of your caching needs.
For the task at hand, you can just use the Application Server's administration console to enable and create the cache:
  1. Login to the admin console. In WebSphere Application Server v6, the URL is something like: http://<server>:9060/ibm/console. The port could be different for your environment. If you don't know the port, ask your system administrator. Or, if you have access to the command line, you can grep the serverindex.xml file usually found in the WebSphere install directory under:
    AppServer/profiles/wp_profile/config/cell/<cellname>/nodes/<nodename>/
    

    Look for "WC_adminhost_secure" as an endPointName or try this command (on one line):
    grep -A 1 "WC_adminhost_secure" 
    <path>/AppServer/profiles/wp_profile/config/cells/<cellname>/nodes/
    <nodename>/serverindex.xml
    

    (If anyone reading this article knows an easier way to find out which port the admin server is running on, send me email. I'll update the article and lavish kudos upon you.)
  2. Now, make sure that the Dynamic Cache Service is enabled. Go to Server => Application servers, and choose WebSphere_Portal.

    Figure 2. Select the WebSphere_Portal server
    Figure 2. Select the WebSphere_Portal server

  3. Next, select Container services => Dynamic Cache Service.

    Figure 3. Select the Dynamic Cache Service
    Figure 3. Select the Dynamic Cache Service

  4. You should see that the Dynamic Cache Service is enabled at server startup; if not, enable it.

    Figure 4. Enable the Dynamic Cache Service
    Figure 4. Enable the Dynamic Cache Service

  5. The service is enabled. So now let's create the cache. Expand Resources => Cache instances => Object cache instances.

    Figure 5. Select Object cache instances
    Figure 5. Select Object cache instances

  6. Click New.
  7. Fill in the required fields and frob (modify) any of the other fields, if you like. See the WebSphere Application Server Information Center, listed in http://www.ibm.com/developerworks/websphere/library/techarticles/0707_lynn/0707_lynn.html#resources, for help with the various settings. That way, you can make an informed decision for your environment. Here's what I set:
    • Name: Parameter Cache
    • JNDI name: services/cache/catalog/parameters
    • Checked User listener context
    • Unchecked Dependency ID support
    The names are arbitrary. Just make sure you set it to something fairly unique and then remember it for when you create your servlet filter and portlet code.
  8. Click OK, and then save the configuration. You have a freshly minted cache to work with.

    Figure 6. Your cache should show up in the list of available caches
    Figure 6. Your cache should show up in the list of available caches

  9. Again, remember the JNDI name or write it down. You will need the JNDI name to do a JNDI lookup in your code. So, either make note of it now or leave this page up in the admin console.
There is an important issue with using the dynamic cache that should be carefully considered. Since it is a cache, it could be completely filled up and not allow one to store anything more. So make sure your cache size is large enough for your application. The range is from 100 to 200,000 cache entries. So, do a little math to figure out how many expected concurrent hits times the number of parameters you'll be caching then arbitrarily double it. Of course, if you really want to find a better number, look in the WebSphere Application Server Info Center and search for "dynamic cache size". You'll end up with a list of performance advisors, monitors, viewers, and troubleshooting tips to assist you in picking the right cache size.
Now that you have a cache in which you can place the parameters, you create the servlet filter that will stash the parameters into the cache. Later, your portlet can retrieve them from the cache.
I used IBM Rational® Software Architect to create my servlet filter; you could use it, Rational Application Developer, or another development environment. The goal is to create a JAR file with your filter class in it.
You first need to create a servlet filter with the code shown in Listing 2 , which I describe in some detail below.
public class ParameterFilter implements Filter {
  
  private DistributedMap map = null;
  
  public ParameterFilter() {…}
  public void destroy() {…}

  public void init(FilterConfig arg0) throws ServletException {
    try {
      InitialContext ic = new InitialContext();
      this.map = (DistributedMap)
               ic.lookup("services/cache/catalog/parameters");
      ic.close();
    }catch(NamingException ne) {
      System.out.println("NamingException error");
      System.out.println(ne.getMessage());
    }
  }
…
}

The first thing task in the init method is to use the JNDI name to look up the cache you created earlier. Assuming you remembered what you used for step 7 above, you'll want to use it here in the InitialContext.lookup method.
The real juicy part of the ServletFilter is the doFilter method.
public class ParameterFilter implements Filter {
…
  public void doFilter(javax.servlet.ServletRequest request,  
                       javax.servlet.ServletResponse response,
                       javax.servlet.FilterChain chain) 
     throws java.io.IOException, javax.servlet.ServletException{
    
     HttpServletRequest httpRequest = (HttpServletRequest)request;
     HttpSession session = httpRequest.getSession();
     Enumeration parms = httpRequest.getParameterNames();
     while(parms.hasMoreElements()) {
       StringBuffer key = new StringBuffer(
                                  (String)parms.nextElement());
       String value = httpRequest.getParameter(key.toString());
       //mangle the key so that it's unique for this session
       key.append(".");
       key.append(session.getId());
       map.put(key.toString(), value);
       System.out.println("Parameter Filter: "+key+" -- "+value);
     }

     // forward the wrapped request to the next filter in the chain
     if (chain != null){
       chain.doFilter(request, response);
     }
   }
}

Get all the parameters from the HttpSession and put each one into the DistributedMap. Then, mangle the parameter names so that they are unique for each session; otherwise, you could end up overwriting another user's data in the map. This code moves all the parameters. If you would prefer to only target certain parameters, you can easily make the changes; that task is left as an optional exercise for the reader.
I'll reemphasize that this is a cache. Because we are caching all the parameters that are passed through the URL, the cache could fill up. So, while I leave the exercise of targeting parameters to the reader, you should implement such a mechanism and also enhance what you learn in this article.
For illustrative purposes, the example code includes a System.out.println statement so that you can look in the portal SystemOut.log file to see your filter in action. You might notice that this code does not handle multiple parameters of the same name. That is, if you had a URL like http://<server>/wps/portal?parm=foo&parm=bar you'd end up overwriting parm in the dynamic cache. You could overcome this restriction fairly easily in several different ways. One way would be to put parameters in the dynamic cache assuming there are multiples; that is, use dynamic cache names that are indexed. For example: parm.0.sessionID=foo, parm.1.sessionID=bar, and so on. Then, you would need to pull them out of the cache in a similar fashion. Another way would be to comma-separate them in the cache. For example: parm.sessionID=foo,bar. There are many ways to marshal the data; it is just a matter of choosing what is right for your application.
When you are finished coding (or in this case copying the code into) your ServletFilter class, build this code into a JAR file.
Now, you are ready to install the filter.
  1. Place the JAR file you created above in the shared/app directory under the portal install directory:
    ${portalserver}/shared/app
    

    On the Linux® server, I used:
    /opt/ibm/WebSphere/PortalServer/shared/app
     

  2. Configure the filter in WebSphere Portal's web.xml file:
    ${appserver}/profiles/wp_profile/config/cells/${server}/applications/wps.ear/
       deployments/wps/wps.war/WEB-INF 
    

    On Linux, I used:
    /opt/ibm/WebSphere/AppServer/profiles/wp_profile/config/cells/icat28/
    applications/wps.ear/deployments/wps/wps.war/WEB-INF

  3. Add the following code to the web.xml file:
    <filter>
       <filter-name>Parameter Filter</filter-name>
       <filter-class>com.ibm.catalog.filters.ParameterFilter</filter-class>
    </filter>
    
    <filter-mapping>
           <filter-name> Parameter Filter</filter-name>
     <url-pattern>/myportal/*</url-pattern>
    </filter-mapping>
    <filter-mapping>
           <filter-name> Parameter Filter</filter-name>
           <url-pattern>/portal/*</url-pattern>
    </filter-mapping> 
    

    This code does two things. First, it defines the filter in the Application Server. Second it tells the Application Server that before you pass the request on to WebSphere Portal, call the Parameter filter class. Because the URL filter mapping filters both "/portal/" and "/myportal/", the filter will get called whether or not you are logged on. (The "/myportal/" is the logged-in portal url.)
  4. Restart the portal server.
  5. After WebSphere Portal is up, you can test the filter. Access the portal by typing something like: http://<servername>/wps/portal?parm=quux
  6. Then, look in the SystemOut.log file in the portal server's log directory. You should see a line that looks similar to the following, (assuming, of course, that you put a System.out.println statement in your ServletFilter code):
    [7/7/07 16:30:24:177 EDT] 0000007c SystemOutO Parameter Filter: 
       parm.dl9vOW6IhS68o0GqTjZS7QQ – quux
    

This didn't work the first time for me either. If it worked for you, YaY! Then I guess this article did its job. For the rest of us, here are the problems I ran into and their fixes.
  1. Error 500, some sort of problem with the filter. For some reason your filter blew up. It happens to all of us. What happened in my case first time I ran it, was that I changed from using a String for my dynamic cache key to a StringBuffer, but I forgot to change the map.put code. Inspect your code, it shouldn't be terribly long and whatever is broken should be easy to spot. If this doesn't work, capture all exceptions and output them to a log file.
  2. My filter never seems to get executed. In my case, I defined the filter in the wrong place so it never got executed. Go back and look at which web.xml file you modified. It's the one in the profiles/wp_profile/config directory, NOT the one in the profiles/wp_profile/installedApps directory! I know seems like a silly mistake, but it happens.
You can use any old portlet for this part. What you will do is pretty non-invasive. You add a few lines to the init method and then add a getParameter method.
  1. Add the following lines of code to your init method:
    public abstract class ParameterPortlet extends GenericPortlet {
      
      private DistributedMap parameterMap = null;
    
      public void init() throws PortletException{
        super.init();
        try {
          InitialContext ic = new InitialContext();
          parameterMap = (DistributedMap) 
                 ic.lookup("services/cache/catalog/parameters");
          ic.close();
        } catch (NamingException e) {
          System.out.println("NamingException in the portlet");
        }
      }
      …
    }

    This code simply looks up the same cache that you defined at the beginning of this article and assigns the DistributedMap to a variable.
  2. Next, add the getParameter method.
    public abstract class ParameterPortlet extends GenericPortlet {
      …
      protected String getParameter(RenderRequest request, String name){
          
          StringBuffer key = new StringBuffer(name);
          key.append(".");
          key.append(request.getPortletSession().getId());
          String parm = (String)parameterMap.get(key.toString());
          System.out.println("Portlet parm: "+key+" -- "+parm);
          parameterMap.remove(key.toString());
          return parm;
      }
    
      …
    }
    

    This code simply grabs the parameter out of the DistributedMap and returns it. It removes the parameter from the map once this method is called; you may or may not what to do that. It's just depends on what you plan to do with the parameters. In this case, I moved them to a request attribute so I'd have further access to them in the rest of the portlet. This is easy to do by adding these few lines of code to the getParameter method:
    …     
    String parm = (String)parameterMap.get(key.toString());
    if(null != parm){
        request.setAttribute(name, parm);
    }
    …

That's all there is to the portlet, other than using the parameters. You could implement other methods in addition to getParameter. Perhaps you need to mirror all the getParameter type methods. Maybe you want this to be more transparent and don't care where your parameters come from. You could use the getParameter method above as wrapper to the request.getParameter method to look in the request and then in the dynamic cache for parameters.
Here's one example of a super-duper getParameter method:
protected String getParameter(RenderRequest request, String name){
      
    StringBuffer key = new StringBuffer(name);
    key.append(".");
    key.append(request.getPortletSession().getId());
    String parm = (String)parameterMap.get(key.toString());
    if(null == parm) {
      parm = request.getParameter(name);
    }
    if(null == parm) {
      parm = (String)request.getAttribute(name);
    }
    if(null != parm) {
      request.setAttribute(name, parm);
    }
    System.out.println("Portlet parm: "+key+" -- "+parm);
    parameterMap.remove(key.toString());
    return parm;
}

You might want to change the order of precedence; for example, a portlet specific parameter might be more important than a URL parameter.
So far, your portlets can read URL parameters, but that doesn't do you much good unless you can get to the page on which the portlet resides. Those of you who know about URL Mappings and Custom Unique Names, can skip to the Conclusion, if you so desire.
The problem with what we have done so far is that unless your portlet is on the default portal page, you still have a very long URL on which to add parameters. You can use URL Mapping to get a URL that points directly to your page.
Here's what you need to do:
  1. Login to WebSphere Portal as an administrator.
  2. Go to the Administration page.
  3. Select Portal Settings => Custom Unique Names => Pages.

    Figure 7. Navigate to Custom Unique Names for Pages
    Figure 7. Navigate to Custom Unique Names for Pages

  4. Search for the page that contains your portlet.
  5. Click the pencil icon and give the page a unique name.

    Figure 8. Set a unique name for your page
    Figure 8. Set a unique name for your page

  6. Now that your page has a unique name, you can set a URL mapping for that page. In my case, I wanted a URL that points directly to my Results page, and I wanted something easy like: http://<servername>/wps/portal/ResultsYou can specify what you want by selecting Portal Settings => URL Mapping, and clicking the New Context button.
  7. Choose a label; in this case, I chose Results, as you can see in the URL above.
  8. Now, click the Edit mapping button (Edit mapping button) in the same row as the Context label you chose.
  9. Find and select the page with the unique name you selected in step 5.You don't really need a custom Unique Name, but it sure is easier that remembering that your page's Unique Identifier is "6_28NFOKG10OD4602PH9OATU2083".
  10. Save everything and you are done.
Your page should now be available by just tacking on the Context label you chose. If you add parameters onto the end of that URL, your portlet will pick them up. For example you could pass a query parameter to the "Results" page above to show the results of that query:
http://<servername>/wps/portal/Results?query=search+me

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. It is very nice & useful.And also very informative article ,I also get some information from
    Agility Insight

    ReplyDelete
  3. very very nice article - come join us now - http://solusisehatanda.com/

    ReplyDelete