Friday, April 10, 2009

WebSphere Portlet Services and Command Cache

1 Creating a Portlet Service
Use portlet services to share re-usable functionality across multiple portlets.
In particular, web service client-side code can be implemented as portlet services providing the service adapter layer of the reference architecture.
Define an interface extended from PortletService (xsd.Account.com.poppywood.AccountInfo is created from the Axis WSDL2JAVA tool and represents the service client implementation code for the service call)

import xsd.Account.com.poppywood.AccountInfo;
import com.ibm.portal.portlet.service.PortletService;
import javax.portlet.*;
public interface IAccountService extends PortletService {

public AccountInfo[] getAccount(PortletRequest request, PortletResponse response) throws Exception;

public AccountInfo[] getAccount(String accountName) throws Exception;
}

Create the Portlet Service Provider class by implementing PortletServiceProvider and your new interface. In this example, the service retrieves an input parameter from a session attribute passed in through the PortletRequest object.


import java.util.prefs.Preferences;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.xml.rpc.ServiceException;
import xsd.Account.com.poppywood.AccountInfo;
import com.ibm.portal.portlet.service.PortletServiceUnavailableException;
import com.ibm.portal.portlet.service.spi.PortletServiceProvider;

public class AccountInfoServiceImpl implements PortletServiceProvider,
IAccountService {

private java.net.URL portAddress;

public void init(Preferences prefs) throws PortletServiceUnavailableException {
String url = prefs.get("portAddress", "http://myserver/Myservice/Account.asmx");
try {
portAddress = new java.net.URL(url);
}
catch (java.net.MalformedURLException e) {
try {
throw new javax.xml.rpc.ServiceException(e);
} catch (ServiceException e1) {
e1.printStackTrace(System.out);
}
}

}

public AccountInfo[] getAccount(PortletRequest request, PortletResponse response) throws Exception {
PortletSession session = request.getPortletSession();
String accountName = (String)session.getAttribute("AccountName");
AccountInfo[] ret = null;
System.out.println("In getAccount(req,resp) with name " + accountName);
if(accountName != null){
ret = getAccount(accountName);
}
return ret;
}

public AccountInfo[] getAccount(String accountName) throws Exception {
AccountInfo[] ret = null;
System.out.println("In getAccount(string) with name " + accountName);
try{
AccountCommand com = new AccountCommand();
com.setAccountName(accountName);
com.setPortAddress(this.portAddress);
if (com.isReadyToCallExecute()){
System.out.println("In getAccount(string) and ready to execute");
com.performExecute();
ret = com.getAccounts();
}
}
catch(Exception e){
e.printStackTrace(System.out);
}
return ret;
}

}

The PortletServiceProvider includes an init method to initialize portlet service preferences. The sample uses the portlet service preferences to fetch the URL of the Web Service it will call. This was done for convenience; it is not the recommended way to configure URL settings.

The Preferences class get method requires two parameters. The first parameter is the preference name, and the second parameter is the default value to use if the parameter is not set. The sample URL parameters are used to initialize the URL settings for use by the Command classes, which are discussed below.

2 Using the Command Cache
A command is typically a function used to fetch data from a repository such as a web service. In order to cache a command, you need a method must to return a unique key. When executing the command for the first time, given a unique key, the implementation logic executes to fetch the data. This data is cached and associated with the unique key. When executing the command again with the same key, the data is simply returned without executing the command logic. The performance gains are significant when you consider the savings gained from not executing potential remote access to external services.
Commands must implement CacheableCommandImpl. The pertinent code is below (omitting the code to build the web service call for clarity)


public class AccountCommand extends CacheableCommandImpl {

private String accountName = null;
private java.net.URL portAddress;
private AccountProxy proxy;
private AccountInfo[] accounts;

public boolean isReadyToCallExecute() {
return accountName != null;
}

//get proxy for web service
private AccountProxy getAccount_PortTypeProxy() {
if (proxy == null) {
proxy = new AccountProxy();
}
return proxy;
}

// ... some code to build Find Account Request

public void performExecute() throws Exception {
try {
System.out.println("performExecuteaccount = " + accountName);
proxy = getAccount_PortTypeProxy();
proxy.setEndpoint(portAddress.toString());
FindAccountResult result = proxy.findAccount(getFindAccountRequest());
if (result.isOverallResult()) {
accounts = result.getAccountDetailResponse();
}
else
{
System.out.println("Error in Account Web Service: " + result.getResult().getException().getMessage());
}
}
catch (Exception ex){
System.out.println("Error in performExecuteaccount = " + accountName);
ex.printStackTrace(System.out);
}
}

public void setAccountName(String accountName) {
this.accountName = accountName;
}

public void setPortAddress(java.net.URL portAddress) {
this.portAddress = portAddress;
}

public AccountInfo[] getAccounts() {
return accounts;
}

}

The abstract class mandates that the concrete subclass command implement these two abstract methods:
boolean isReadyToCallExecute() Should return true if the command is ready to execute. In most cases, it involves checking that all required parameters have been provided by the caller.
void performExecute()Contains the execution logic used to fetch and store the data as an instance variable within the command.
Two other methods are required. One is used to return the data stored in the instance variable. The other method returns the unique key. The unique key’s getter method is registered in the cachespec.xml file, which is discussed below when configuring the Command Cache.
This file defines the Portal’s command cache. The settings below (for the sample) would be merged with
/installedApps//wps.ear/wps.war/WEB-INF/cachespec.xml






command

shared
com.poppywood.portletService.AccountCommand



true

1
600




The portlet service must also be configured. These settings (for the sample) need to be merged with \PortalServer\shared\app\config\services\PortletServiceRegistryService.properties


#Merge this file with \PortalServer\shared\app\config\services\PortletServiceRegistryService.properties
# Account Portlet Service
jndi:com.poppywood.portletService.IAccountService=com.poppywood.portletService.AccountInfoServiceImpl
com.poppywood.portletService.AccountInfoServiceImpl.portAddress=http://myserver/Myservice/Account.asmx

3 Consuming the Portlet Service from a Portlet
The portlet must initialize the portlet service:

public void init() throws PortletException {
super.init();
try{
javax.naming.Context ctx = new javax.naming.InitialContext();
Object home = ctx.lookup("portletservice/com.poppywood.portletService.IAccountService");
if (home != null)
accountInfoServiceHome = (PortletServiceHome) home;

} catch(Exception ex) {
System.out.println("AccountService is not found");
ex.printStackTrace(System.out);
}
}

To access the functionality of the portlet service set a session attribute in the PortletRequest and then call the service:

private void retrieveAccounts(PortletRequest request, PortletResponse response, AccountInfoRetrievalSessionBean sessionBean) {
accountInfoService=(IAccountService)
accountInfoServiceHome.getPortletService
(IAccountService.class);
PortletSession session = request.getPortletSession();
session.setAttribute("AccountName", "Test Account Name");
sessionBean.setAccounts(accountInfoService.getAccount(request, response));
}