Saturday, November 30, 2013

RPC Vs Document style web service

Our goal is to identify differences between implementation and invocation of RPC Vs Document style web services. At the same time we’ll also analyze the changes in WSDL and Client for these two styles of web services.

Currently I’m reading Java Web Services [Up and Running] – 2ndEdition; will refer the exercises presented in Chapter 1: An Example with Richer Data Types.
  
I’m using Java SE7 with command line to compile the classes and run the utilities. You can use any version of Java SE6 that ships with JAX-WS.

Let’s start.

The exercise mentioned above is all about creating a Web service that provides information about a ‘Team’ and all its ‘Players’. You can request the web service to provide more information on individual team.

Our Service class (Service Implementation Bean) is Teams.java.

Teams.java

package ch01.team;

import java.util.List;
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;

@WebService
@SOAPBinding(style=Style.DOCUMENT)
public class Teams {
    private TeamsUtility utils;

    public Teams() {
       utils = new TeamsUtility();
       utils.make_test_teams();
    }

    @WebMethod
    public Team getTeam(TeamQuery query) { return utils.getTeam(query.getTeamQueryParams()); }

 }

Annotation @avax.jws.WebMethod on top of the method makes it a service operation.

Annotation @javax.jws.WebService marks a Java class as implementing a Web Service, or a Java interface as defining a Web Service interface. For our case, I would also have declared an interface with above methods and marked that with @WebService annotation, that would have been our Service Endpoint Interface(SEI) and our implementation class (Teams) can 'implements' this interface to provide the implementation. However we didn't do that to keep things simple. In actual implementation it's always better to write off a separate interface declaring all your service methods.

Annotation @javax.jws.soap.SOAPBinding specifies the mapping of the Web Service onto the SOAP message protocol. The default value is 'Style.Document', you could also specify 'Style.RPC'

Straight from the book: The document style indicates that a SOAP-based web service’s underlying messages contain full XML documents; for instance, a company’s product list as an XML document or a customer’s order for some products from this list as another XML document. By contrast, the rpc style indicates that the underlying SOAP messages contain parameters in the request messages and return values in the response messages.

To understand this, let's see 2 Java method that takes Customer Id and returns payment information for consumer. 

RPC way: public String getPaymentInfo(int custId, String custName, int age); or

Document way: public PaymentVO getPaymentInfo(Customer cust);

Now let's assume you've to write a method for validating the Customer information before returning the Payment information, and your validator implementation expects a 'Customer' object in validate method: public boolean isValid(Customer cust);

In the method marked with RPC ways, the information is exchanged through parameters, there is no concept of any entity, the parameters are of basic Java type and creating a validation logic around these parameters needs extra effort. You need to create a domain related entity (Customer in this case) and populate that with these parameters to get a meaningful Object. 

The 'Document' style method is more OO friendly by referring to entities like 'Customer' and 'PaymentVO'. If we've a validation logic for Customer entity as mentioned above, we could certainly use that here. Since parameters have defined type, we could write all possible of logic around those parameters and reuse them wherever these entities appears. 

Let's see what kind of information is exchanged between the server and client for these styles. Below are other Java classes used by Teams web service.

TeamQuery.java
package ch01.team;

public class TeamQuery {
private String teamQueryParams;
public String getTeamQueryParams() {return teamQueryParams;}
public void setTeamQueryParams(String teamQueryParams) {this.teamQueryParams = teamQueryParams;}
}

Player.java

package ch01.team;

public class Player {

    private String name;

    private String nickname;


    public Player() { }
    public Player(String name, String nickname) {
       setName(name);
       setNickname(nickname);
    }

    public void setName(String name) { this.name = name; }
    public String getName() { return name; }
    public void setNickname(String nickname) { this.nickname = nickname; }
    public String getNickname() { return nickname; }
}

Team.java
package ch01.team;

import java.util.List;
public class Team {
    private List<Player> players;
    private String name;
    
    public Team() { }
    public Team(String name, List<Player> players) { 
       setName(name);
       setPlayers(players); 
    }

    public void setName(String name) { this.name = name; }
    public String getName() { return name; }
    public void setPlayers(List<Player> players) { this.players = players; }
    public List<Player> getPlayers() { return players; }
}

TeamsUtility.java
package ch01.team;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;

public class TeamsUtility {
    private Map<String, Team> team_map;

    public TeamsUtility() {
team_map = new HashMap<String, Team>();
make_test_teams();
    }

    public Team getTeam(String name) {
return team_map.get(name);
    }

    public void make_test_teams() {
List<Team> teams = new ArrayList<Team>();

Player burns = new Player("George Burns", "George");
Player allen = new Player("Gracie Allen", "Gracie");
List<Player> ba = new ArrayList<Player>();
ba.add(burns); ba.add(allen);
Team burns_and_allen = new Team("Burns and Allen", ba);
teams.add(burns_and_allen);

Player abbott = new Player("William Abbott", "Bud");
Player costello = new Player("Louis Cristillo", "Lou");
List<Player> ac = new ArrayList<Player>();
ac.add(abbott); ac.add(costello);
Team abbott_and_costello = new Team("Abbott and Costello", ac);
teams.add(abbott_and_costello);

Player chico = new Player("Leonard Marx", "Chico");
Player groucho = new Player("Julius Marx", "Groucho");
Player harpo = new Player("Adolph Marx", "Harpo");
List<Player> mb = new ArrayList<Player>();
mb.add(chico); mb.add(groucho); mb.add(harpo);
Team marx_brothers = new Team("Marx Brothers", mb);
teams.add(marx_brothers);

store_teams(teams);
    }

    private void store_teams(List<Team> teams) {
for (Team team : teams)
   team_map.put(team.getName(), team);
    }
}

TeamsPublisher.java

package ch01.team;
import javax.xml.ws.Endpoint;
class TeamsPublisher {
    public static void main(String[ ] args) {
       int port = 8888;
       String url = "http://localhost:" + port + "/teams";
       System.out.println("Publishing Teams on port " + port);
       Endpoint.publish(url, new Teams());
    }

Publishing the Web Service (Document Style)
TeamsPublisher uses JAX-WS 'Endpoint' class that can be used for publishing the web services locally or in development environments. 

Since we're using 'document' style for our web service, we're dealing with typed messages, i.e. our 'Request' and 'Response' messages will belong to a specific XML schema type. To publish the web service, we also need to define these types, that will allow our WSDL to display these data types. In other words, the artifacts produced by wsgen are the Java types from which theXML Schema types for the messages are derived

'wsgen' utility that ships with JAX-WS can develop the Java types after scanning our Service Java classes. 

In your working directory, invoke this utility from command line:

% wsgen -keep -cp . ch01.team.Teams

Note: In JDK 1.7 you don't need togenerate these classes by 'wsgen' utility. EndPoint automatically generates these classes for you.

Executing this command creates two class files in ch01.team.jaxws package.

GetTeam.java
package ch01.team.jaxws;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement(name = "getTeam", namespace = "http://team.ch01/")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "getTeam", namespace = "http://team.ch01/")
public class GetTeam {

    @XmlElement(name = "arg0", namespace = "")
    private ch01.team.TeamQuery arg0;
    public ch01.team.TeamQuery getArg0() {return this.arg0;}
    public void setArg0(ch01.team.TeamQuery arg0) {this.arg0 = arg0;}
}

GetTeamResponse.java
package ch01.team.jaxws;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement(name = "getTeamResponse", namespace = "http://team.ch01/")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "getTeamResponse", namespace = "http://team.ch01/")
public class GetTeamResponse {

    @XmlElement(name = "return", namespace = "")
    private ch01.team.Team _return;

    public ch01.team.Team getReturn() {return this._return;}
    public void setReturn(ch01.team.Team _return) {this._return = _return;}
}

As mentioned above, the two XML schema type for our web service must be 'GetTeam' and 'GetTeamResponse'. More important is to note that our web service has one service method 'getTeam' and Java types developed by 'wsgen' is 'GetTeam' and 'GetTeamResponse' that represents request and response for our web service. if this web service has additional service operations 'getPlayer()' then wsgen would have generated two more classes 'GetPlayer()' and 'GetPlayerResponse()' for request and response fot this service method.

Let's execute 'TeamPublisher' and publish the web service.

% java TeamPublisher


WSDL Details (Document style)

Type http://localhost:8888/teams?wsdl in address bar to see the WSDL



The <types> element displays the XML schema data type used by the web services. 

Type this URL http://localhost:8888/teams?xsd=1 in address bar to see all the data types used by this web service.




As we mentioned above 'wsgen' created two Java classes that represents data types 'GetTeam' and 'GetTeamResponse', corresponding elements are highlighted above. Rest of the document elaborate more on the data definition of these two operations.

The top element for this data structure is '<getTeam>' and '<getTeamResponse>' that relates to Request and Response for this web service and all other elements defined in this data structure are sub-element of these top level elements. This structure gives the notion of 'document' for the document style web services, i.e. both Request and Response is a complete XML document with one top level elements that contains other sub elements. This structure also gives you flexibility to specify all kind of possible data structures for your service operations. 


The <message> elements represents the messages involved in invoking this service operations, one each for input and output. Each <message> contains a <part> element that represents the parameters expected by the request or values returned by the response. The XML structure for these parameters are defined in the <types> element.



The portType section presents the service as named operations, with each operation
as one or more messages. Note that the operations are named after methods annotated

as @WebMethods (getTeam in our case). By default the input message name would be same as that of service operation and output would be 'Response' followed by input message name [getTeamResponse in this case]. A web service’s portType is similar to a Java interface in presenting the service abstractly, that is, with no implementation details. The implementation details is presented in <binding> element.



A WSDL binding is akin to a Java implementation of an interface (that is, a WSDL portType). 
The 'name' attribute defines the name of the binding, and the type attribute points to the port for the binding, in this case the "tns:Teams" port. Like a Java implementation class, a WSDL binding provides important concrete details about the service. <binding> element shows some low level details like protocol to be used for this web services(http in this case indicates SOAP messages will be sent and received over HTTP), style="document" indicates Style used by this service. "use=literal" in this case indicates how "something" is encoded/serialized to XML and then later decoded/de-serialized from XML back to "something" . See more about these rules here.




The <service> element lists one or more port elements, where a port consists of a portType together with a corresponding binding (implementation).


Publishing the Web Service (RPC Style)

Now lets publish this web service again with RPC style. There will not be much changes in our source code and it's simpler than 'Document' style to create a web service in RPC style.

Our web service class will now have SOAPBinding defined as 'Style.RPC'. 

package ch01.team;

import java.util.List;
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;

@WebService
@SOAPBinding(style=Style.RPC)
public class Teams {
    private TeamsUtility utils;

    public Teams() { 
       utils = new TeamsUtility(); 
       utils.make_test_teams();
    }

    @WebMethod
    public Team getTeam(TeamQuery query) { return utils.getTeam(query.getTeamQueryParams()); }
}

Running the 'TeamsPublisher' on command line will publish this web service.. wait!!! what about 'wsgen' classes? 

We don't need 'wsgen' utility in case of RPC style of web services because we're not going to define any specific XML schema type for our 'Request' and 'Response' message here. This is one of the main reason for RPC style to be developer's friendly also.

Type this URL (http://localhost:8888/teams?wsdl) in address bar to see the updated WSDL.

 
The <types> element shows the schema URL that contains data types used in service operations. Not much change here.

Type the schema URL in address bar to see the data types defined by the RPC style service.


We could see all the data types for parameters involved in service call, 'Player', 'Team' etc.. however if you compare this with data types defined by Document style, you would notice that 'Request' and 'Response' data types ('GetTeam' and 'GetTeamResponse') are missing here. Also you do not have any top level element like what we had in Document style. With this schema, you cannot validate your Request and Response XML structure.

The <message> element (below) contains a <part> for every parameters expected by the service operation. We could have used the @WebParam annotation to specify some meaningful name for the input parameter that would be displayed in WSDL in place of 'arg0'.

The attribute 'type' specify the schema type for parameters. 


There is no change in the <portType> element. As we mentioned above <portType> is akin to a Java interface. Since there is no change in the interface (or service methods), corresponding <portType> is not changed.


The <binding> element shows the style used by this web service (that is RPC).



<service> element is again not changed.


Generate the Client Code (Document Style)

Let's create the client classes for document style web service first. JAX-WS's 'wsimport' utility can do that for us.

Execute below command in your working directory.

% wsimport -p teamsDocClient -keep http://localhost:8888/teams?wsdl

Running this command generates 9 classes, five of which are for XML Schema type defined in our web service:
GetTeam
GetTeamResponse
Team
- Player
- TeamQuery

Two classes are for Web Service and 'Port Type' defined by our service:
- TeamsService
- Teams

Two other classes are used by JAXB for marshalling/unmarshalling the SOAP request and response.

ObjectFactory
package-info

The 'Teams' interface generated by wsimport utility contains the same method 'getTeam()' defined in our Service implementation Bean.

Teams.java
package teamsDocClient;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;

@WebService(name = "Teams", targetNamespace = "http://team.ch01/")
@XmlSeeAlso({ObjectFactory.class})
public interface Teams {
    @WebMethod
    @WebResult(targetNamespace = "")
    @RequestWrapper(localName = "getTeam", targetNamespace = "http://team.ch01/", className = "teamsDocClient.GetTeam")
    @ResponseWrapper(localName = "getTeamResponse", targetNamespace = "http://team.ch01/", className = "teamsDocClient.GetTeamResponse")
    public Team getTeam(
        @WebParam(name = "arg0", targetNamespace = "")
        TeamQuery arg0);
} 

When you invoke the 'getTeam()' method,you also provide certain input parameters, TeamQuery' in this case. This information need to be marshalled before actual service method gets invoked at the server. @RequestWrapper does marshalling for us. The 'localname' property specifies the XML element name that wraps our service request.

Same principle applies to @ResponseWrapper, except that this is used for unmarshalling the response.

'className' property specifies the Java class that implements these wrappers.

TeamsService
package teamsDocClient;



import java.net.MalformedURLException;

import java.net.URL;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.WebEndpoint;
import javax.xml.ws.WebServiceClient;
import javax.xml.ws.WebServiceFeature;
@WebServiceClient(name = "TeamsService", targetNamespace = "http://team.ch01/", wsdlLocation = "http://localhost:8888/teams?wsdl")
public class TeamsService
    extends Service
{
    private final static URL TEAMSSERVICE_WSDL_LOCATION;
    private final static Logger logger = Logger.getLogger(teamsDocClient.TeamsService.class.getName());
    static {
        URL url = null;
        try {
            URL baseUrl;
            baseUrl = teamsDocClient.TeamsService.class.getResource(".");
            url = new URL(baseUrl, "http://localhost:8888/teams?wsdl");
        } catch (MalformedURLException e) {
        }
        TEAMSSERVICE_WSDL_LOCATION = url;
    }

    public TeamsService(URL wsdlLocation, QName serviceName) {super(wsdlLocation, serviceName);}
    public TeamsService() {super(TEAMSSERVICE_WSDL_LOCATION, new QName("http://team.ch01/", "TeamsService"));}

    @WebEndpoint(name = "TeamsPort")
    public Teams getTeamsPort() {
        return super.getPort(new QName("http://team.ch01/", "TeamsPort"), Teams.class);
    }

    @WebEndpoint(name = "TeamsPort")
    public Teams getTeamsPort(WebServiceFeature... features) {
        return super.getPort(new QName("http://team.ch01/", "TeamsPort"), Teams.class, features);
    }
}

The TeamService class has a default constructor to instantiate the web service and a method 'getTeamPort' to get access to web service port, from which we can instantiate the service methods. 

Let's write the Client for this web service.

Client.java
package teamsDocClient;

public class Client {
public static void main(String s[]){
TeamsService service = new TeamsService();//Invoke the Service
Teams port = service.getTeamsPort();//Get access to the the port

//Prepare query parameters
TeamQuery query = new TeamQuery();
query.setTeamQueryParams(("Burns and Allen"));
Team team = port.getTeam(query);//Call the service methods
System.out.println("Team name: "+ team.getName());
for(Player p: team.getPlayers()){
System.out.println("Player name: "+ p.getName());
System.out.println("Player nick name: "+ p.getNickname());
}
}
}

Output:
Team name: Burns and Allen
Player name: George Burns
Player nick name: George
Player name: Gracie Allen
Player nick name: Gracie

Creating the Client class is fun, invoke the service, get the port and call the methods. Quite simple!

See the Request/Response Structure (Document)

Let's see the information exchange between the client and server while invoking getTeam() method. I've used 'tcpmon' utility to capture this information.

Request XML


<?xml version="1.0"?>

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">

<S:Body>
<ns2:getTeam xmlns:ns2="http://team.ch01/">
<arg0>
<teamQueryParams>Burns and Allen</teamQueryParams>
</arg0>
</ns2:getTeam>
</S:Body>
</S:Envelope>

The structure of request XML matches with data types defined in WSDL schema for document style web service (type="tns:getTeam")

Response XML 
<?xml version="1.0"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getTeamResponse xmlns:ns2="http://team.ch01/">
<return>
<name>Burns and Allen</name>
<players>
<name>George Burns</name>
<nickname>George</nickname>
</players>
<players>
<name>Gracie Allen</name>
<nickname>Gracie</nickname>
</players>
</return>
</ns2:getTeamResponse>
</S:Body>
</S:Envelope>

Similarly response XML is a complete document in itself that matches with type="tns:getTeamResponse" defined in XML schema.

Generate the Client Code (RPC Style)

We can follow the similar steps to create Client code for our RPC style web service. 

wsimport -p teamsRpcClient -keep http://localhost:8888/teams?wsdl

This will create 7 classes in teamsRpcClient directory. (2 lesser than document counterpart). The missing classes are 
GetTeam and 
GetTeamResponse

These are missing because our schema for RPC style web service has no data type for Request and Response. RPC style of web services are not strongly typed and the request and response lack document structure. 

All other classes are present though their structure is slightly changed.

Let's see the Teams class.

Teams.java

package teamsRpcClient;

import javax.jws.WebMethod;

import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.annotation.XmlSeeAlso;

@WebService(name = "Teams", targetNamespace = "http://team.ch01/")
@SOAPBinding(style = SOAPBinding.Style.RPC)
@XmlSeeAlso({
    ObjectFactory.class
})
public interface Teams {
    @WebMethod
    @WebResult(partName = "return")
    public Team getTeam(
        @WebParam(name = "arg0", partName = "arg0")
        TeamQuery arg0);
}

@WebResult annotation specifies the name of the return value as it appears in the WSDL. 


As you can see the 'name' attribute for <part> corresponding to 'getTeamresponse' is names as 'return'. 

@WebParam Specifies the name of the parameter as it appears in the WSDL. ou can see the 'name' attribute of <part> element for 'getTeam' message.

There is no other change in the generated classes. 

We can also use the same Client developed for document style web service to invoke the service operation.

Interestingly the Request and Response XML is also same as its 'document' style counterpart.

Summary 
From implementation perspective, I didn't see much differences between RPC and Document style web service (for this simple example), and also found that understanding a 'Document' style  web service is easier than its RPC counterpart. 

References




1 comment:

  1. Thanks, nice explanation and example. It helps me a lot.

    ReplyDelete