Sunday, 5 April 2015

JAX-WS WebSevice Implementation using Top Down Approach


This post is about developing a JAX-WS web service and deploying on tomcat 6.
The top down approach is used to develope Web Service.

Environment :

Eclipse Juno
Tomcat 6
Apache maven 3
JAX-WS reference implementation - Sun's Metro

Steps to create Web Service :

1)Create a WSDL file using eclipse WSDL editor :

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.atulajoshi.org/userDetails/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="userDetails" targetNamespace="http://www.atulajoshi.org/userDetails/">
  <wsdl:types>
    <xsd:schema targetNamespace="http://www.atulajoshi.org/userDetails/">
      <xsd:element name="getUserDetails">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="userId" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
      <xsd:element name="getUserDetailsResponse">
        <xsd:complexType>
          <xsd:sequence>
          <xsd:element name="userFirstName" type="xsd:string" />
          <xsd:element name="userLastName" type="xsd:string"></xsd:element>
          <xsd:element name="userAddress" type="xsd:string"></xsd:element>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="getUserDetailsRequest">
    <wsdl:part element="tns:getUserDetails" name="parameters"/>
  </wsdl:message>
  <wsdl:message name="getUserDetailsResponse">
    <wsdl:part element="tns:getUserDetailsResponse" name="parameters"/>
  </wsdl:message>
  <wsdl:portType name="userDetails">
    <wsdl:operation name="getUserDetails">
      <wsdl:input message="tns:getUserDetailsRequest"/>
      <wsdl:output message="tns:getUserDetailsResponse"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="userDetailsSOAP" type="tns:userDetails">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="getUserDetails">
      <soap:operation soapAction="http://www.atulajoshi.org/userDetails/getUserDetails"/>
      <wsdl:input>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="userDetails">
    <wsdl:port binding="tns:userDetailsSOAP" name="userDetailsSOAP">
      <soap:address location="http://localhost:8080/UserDetailsService"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>


2)Create a maven project with archtype as webapp. It will create a pom.xml file.
  Add a jax-ws maven plugin with wsimport as goal. Below is the pom.xml I used.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.atul</groupId>
  <artifactId>jaxws</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>jaxws Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
     <dependency>
       <groupId>com.sun.xml.ws</groupId>
       <artifactId>jaxws-rt</artifactId>
       <version>2.2.8</version>
    </dependency>  
  </dependencies>
  <build>
    <finalName>jaxws</finalName>
     <plugins>
     <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-compiler-plugin</artifactId>
       <version>3.1</version>
       <configuration>
         <source>1.7</source>
         <target>1.7</target>
       </configuration>
     </plugin>      
     <plugin>
           <groupId>org.jvnet.jax-ws-commons</groupId>
           <artifactId>jaxws-maven-plugin</artifactId>
           <version>2.3</version>
           <executions>  
   <execution>
     <id>execution</id>
 <goals>
<goal>wsimport</goal>
 </goals>
 <configuration>
   <wsdlDirectory>src/wsdl</wsdlDirectory>
 <packageName>com.atul.jaxws.service</packageName>
 <sourceDestDir>src/main/java</sourceDestDir>
 </configuration>
 </execution>
</executions>
         </plugin>      
    </plugins>
  </build>
  <repositories>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net Repository for Maven</name>
<url>http://download.java.net/maven/2</url>
</repository>
  </repositories>
</project>



3)Import this project into eclipse.
4)Now run mvn clean install
5)It will generate following important classes  :

1)UserDetails.java : an interface corresponding to service element in wsdl with a method same as operation element defined in portType element.
2)GetUserDetails.java and GetUserDetailsResponse.java : corresonding to input and output message parts.
3)UserDetails_Service.java : A service stub used by client

6)Now implement a web service by implementing the interface UserDetails as below

package com.atul.jaxws.service;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebParam.Mode;
import javax.xml.ws.Holder;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
import javax.jws.WebService;


@WebService(serviceName = "userDetails", portName = "userDetailsSOAP",
targetNamespace = "http://www.atulajoshi.org/userDetails/", wsdlLocation = "userDetails.wsdl",
endpointInterface = "com.atul.jaxws.service.UserDetails")
public class UserDetailsImpl implements UserDetails {

@WebMethod(action = "http://www.atulajoshi.org/userDetails/getUserDetails")
@RequestWrapper(localName = "getUserDetails", targetNamespace = "http://www.atulajoshi.org/userDetails/", className = "com.atul.jaxws.service.GetUserDetails")
@ResponseWrapper(localName = "getUserDetailsResponse", targetNamespace = "http://www.atulajoshi.org/userDetails/", className = "com.atul.jaxws.service.GetUserDetailsResponse")
public void getUserDetails(
@WebParam(name = "userId", targetNamespace = "") String userId,
@WebParam(name = "userFirstName", targetNamespace = "", mode = Mode.OUT) Holder<String> userFirstName,
@WebParam(name = "userLastName", targetNamespace = "", mode = Mode.OUT) Holder<String> userLastName,
@WebParam(name = "userAddress", targetNamespace = "", mode = Mode.OUT) Holder<String> userAddress) {
// TODO Auto-generated method stub
System.out.println("input param got .."+userId);

userFirstName.value = "Atul";
userLastName.value = "Joshi";
userAddress.value = "Pune";


}

}
The important thing to note here is parameters with Holder classes (userFirstName ,userLastName and userAdress). Please see how these parameters are set with values which are sent back to client. Also please note that their mode is set as OUT meaning they will be filled up with values and sent back to client.

7)Run maven : mvn clean install

8)Since we are deploying this on tomcat6 with Sun'S RI, sun-jaxws.xml should be added which should look like below : (web.xml should be standard one using WSServlet and corresponding listener for Sun's RI)

<?xml version="1.0" encoding="UTF-8"?>
<endpoints
    xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
    version="2.0">

    <endpoint
        name="userDetails"
        implementation="com.atul.jaxws.service.UserDetailsImpl"
        url-pattern="/UserDetailsService" />

</endpoints>

8)Deploy the .war file in tomcat.I prefer modofying server.xml file to deploy the exploded web app directory on tomcat.
Following is the Host element snippet from server.xml file
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true" xmlNamespaceAware="false" xmlValidation="false">

        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/>
        -->
      <Context docBase="F:/study/jaxws/target/jaxws" path="/jaxws" reloadable="true"/>
     
      </Host>

9)Access the WSDL of web service by :

http://localhost:8080/jaxws/UserDetailsService?wsdl

Problems Faces :
1)While running mvn clean install , I got below maven error :
Failed to execute goal org.jvnet.jax-ws-commons:jaxws-maven-plugin:2.3:wsimport (execution2) on project jaxws: Execution execution2 of goal org.jvnet.jax-ws-commons:jaxws-maven-plugin:2.3:wsimport failed: String index out of range: -1 -> [Help 1]
After I made my eclipse use JDK path as JVM instead of JRE , this error got resolved.

2)The class implementing the interface must be annotate with @WebService annotation.

Friday, 20 February 2015

Migrating JSF 1.2 - JSF2 and RichFaces 3 to 4

The motive of this blog post is to share my experience of migrating an application from JSF 1.2 to JSF 2.1  and RichFaces 3.3 to 4.5.

To start with , following is the custom facelet component designed to implement sorting in rich:dataTable

Sorting In RichDataTable : 

Since built in sorting has been removed for rich:dataTable , the sorting has to be achieved programmatically.  After referring rich faces showcase in point 3 ,  I have designed a new facelet custom component with supporting java class as shown below :

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:a4j="http://richfaces.org/a4j"
                xmlns:c="http://java.sun.com/jsp/jstl/core"
                xmlns:fn="http://java.sun.com/jsp/jstl/functions">
             
<ui:composition>

<h:panelGrid columns="1" styleClass="table_center">
<a4j:commandLink value="#{name}" styleClass="dataTableSortLink" actionListener="#{bean[actionListenerMethod]}" render="#{render}">
              <f:param name="columnKey" value="#{columnKey}"/>
              <span style="padding:1px;"/>
              <h:graphicImage value="/images/icons/arrow-unsorted.jpg" alt="unsorted" styleClass="sortingArrows" rendered="#{sortOrder eq 'unsorted'}"/>
  <h:graphicImage value="/images/icons/arrow-ascending.jpg" alt="ascending" styleClass="sortingArrows" rendered="#{sortOrder eq 'ascending'}"/>
  <h:graphicImage value="/images/icons/arrow-descending.jpg" alt="descending" styleClass="sortingArrows" rendered="#{sortOrder eq 'descending'}"/>              
</a4j:commandLink>
</h:panelGrid>                
</ui:composition>
</html>

and it will be invoked from rich:dataTable as shown below :

<rich:dataTable id="userList" rows="#{bean.rowsPerPage}" value="#{bean.userList}" var="user">

<rich:column sortBy="#{user.name}" sortOrder="#{bean.dataSorterHelper.sortOrderMap['name']}">
<f:facet name="header">
<custom:commandLinkwithSort name="User Name" bean="#{bean.dataSorterHelper}"
                actionListenerMethod="processSortOrder" render="userList" columnKey="name"
                sortOrder="#{bean.dataSorterHelper.sortOrderMap['name']}"/>
</f:facet>
<h:outputText value="#{user.name}"/>
</rich:column>

<rich:column sortBy="#{user.lastname}" sortOrder="#{bean.dataSorterHelper.sortOrderMap['lastname']}">
<f:facet name="header">
<custom:commandLinkwithSort name="User Last Name" bean="#{bean.dataSorterHelper}"
                actionListenerMethod="processSortOrder" render="userList" columnKey="lastname"
                sortOrder="#{bean.dataSorterHelper.sortOrderMap['lastname']}"/>
</f:facet>
<h:outputText value="#{user.lastname}"/>
</rich:column>

</rich:dataTable>


The class used to handle sorting is shown below :
/**
 * Class to help in sorting rich:dataTable.
 * It contains sort keys for columns to sort and processes sort order according to user selected column for sort.
 *
 */

public class DataSorterHelper {

//map to store sort order for columns , this map contains column key and rich faces sort order
private Map<String,SortOrder> sortOrderMap;

public Map<String, SortOrder> getSortOrderMap() {
return sortOrderMap;
}
/**
* Constructor to initialise sortOrderMap with all columns sort order - unsorted
*
*/
public DataSorterHelper(List<String> sortKeys) {

sortOrderMap = new HashMap<String, SortOrder>();
for(String sortKey : sortKeys){
sortOrderMap.put(sortKey,SortOrder.unsorted);
}
}

public DataSorterHelper() {
sortOrderMap = new HashMap<String, SortOrder>();
}
/**
* Method to set user defined sort order for particular column.Some tables require to set default sort order for a column
*
*/
public void init(List<String> sortKeys,String columnKey,SortOrder sortOrder) {

for(String sortKey : sortKeys){

if(sortKey.equalsIgnoreCase(columnKey)){
sortOrderMap.put(sortKey,sortOrder);
}else{
sortOrderMap.put(sortKey,SortOrder.unsorted);
}
}
}

private void setOtherColumnsUnsorted(String sortKey){

if(sortOrderMap != null){
for(Entry<String,SortOrder> sortInfo : sortOrderMap.entrySet()){
if(!sortKey.equals(sortInfo.getKey())){
sortInfo.setValue(SortOrder.unsorted);
}
}
}
}

/**ActionListener to set sorting order of a column under sorting
* It also sets sorting order of other columns to "unsorted"
* @param event
*/
public void processSortOrder(ActionEvent event){
//get colun key and set sort order
String columnKey = VariableStore.getRequestParameter("columnKey");

/*set sort order of other columns to unsoted .Otherwise sorting doesn't work */
setOtherColumnsUnsorted(columnKey);
if(sortOrderMap != null){
if(sortOrderMap.get(columnKey).equals(SortOrder.ascending)){
sortOrderMap.put(columnKey, SortOrder.descending);
}else{
sortOrderMap.put(columnKey, SortOrder.ascending);
}
}

}

public void resetSortOrderMap(){

if(sortOrderMap != null){
for(Entry<String,SortOrder> sortInfo : sortOrderMap.entrySet()){
sortInfo.setValue(SortOrder.unsorted);
}
}
}


}

It is important to note that , is is necessary to set sort order of other columns to "unsorted" when applying sorting to a prticular column.