|
Using String in the SOAP Body
[Page 1] [Page 2] [Page 3] [Page 4] [Page 5] [Page 6]
The XML document can be passed as a String between the service and the service consumer. This provides a simple option for passing complex business documents without the need for creating serializers or performing encoding-decoding on the underlying elements. The following example demonstrates this strategy.
Example
This example is similar to the HelloWorld example which starts with Java code and generates the WSDL, only that the argument String now corresponds to an XML document based on the XML schema for the purchase order (Code Sample 4). Code Samples 23 and 24 show the service endpoint interface and implementation, respectively. The implementation simply echoes back the same string that it received. This example can be built and deployed using the document-literal formatting (see build file and sources in the <Doc-Literal-String-XML> directory).
Code Sample 23: Service Endpoint Interface
package com.examples.xmlstring;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IStringService extends Remote {
public String sayXMLHello(String xml) throws RemoteException;
}
|
Code Sample 24: Service Implementation
package com.examples.xmlstring;
import java.rmi.RemoteException;
import java.util.Date;
public class StringServiceImpl implements IStringService {
public String sayXMLHello(String xml){
// do something with the XML
System.out.println(xml);
// return a response, in this case the same XML back
return xml;
}
}
|
The client can pass the business document to the service after converting it to a string. The sample client code below reads the XML file containing the instance of the schema and invokes the service.
Code Sample 25: Sample Client
public class WSDLClient {
public static void main(String[] args) throws Exception {
XMLStringService_Impl serviceproxy = new XMLStringService_Impl();
IStringService_Stub stub = (IStringService_Stub) (serviceproxy.getIStringServicePort());
stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY, url);
sendString(XML, stub);
sendStringFromFile("bidrequest.xml", stub);
public static void sendString(String xml, IStringService_Stub stub) throws Exception {
SayXMLHello request = new SayXMLHello(XML);
SayXMLHelloResponse response = stub.sayXMLHello(request);
System.out.println("Server replied back with : \n " + response.getResult());
}
public static void sendStringFromFile(String filename, IStringService_Stub stub) throws Exception {
SayXMLHello request = new SayXMLHello(readStringFromFile(filename));
SayXMLHello request = new SayXMLHello(readStringFromFileRaw(filename));
SayXMLHelloResponse response = stub.sayXMLHello(request);
System.out.println("Server replied back with : \n " + response.getResult());
}
|
The JAX-RPC runtime will create the appropriate escape characters for the tags and quotes in the XML, as can be seen in the SOAP request and response (Code Samples 26 and 27). The String received by the server in its implementation class will be the original XML that was stored in the file on the client side.
Code Sample 26: SOAP Request
POST /xmlstring/jaxrpc HTTP/1.1
Content-Type: text/xml; charset="utf-8"
Content-Length: 1397
SOAPAction: ""
User-Agent: Java/1.4.2_03
Host: localhost:9090
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
<?xml version="1.0" encoding="UTF-8"?>
<env:envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:ns0="http://www.examples.com/types">
<env:body>
<ns0:sayxmlhello>
<string_1><payload>
<BidRequest BidId="123654" DueDate="2004-05-01"
xmlns:n="http://bidservice.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="BidRequest.xsd">
<Company Name="Small Time Shipping" id="10001" url="www.BigTimeShipping.com"
phone="888-920-9817">
<Address Street="Small Ship Lane" City="Oceanana" State="MD" Zip="20819"/>
<BiddingAgent Name="Joe Bid Agent" Email="joe@shipping.com"
Phone="888-890-1789"/>
</Company>
<Manifest>
<PickupPortOfCall Port="Miami" Date="2003-10-09" Time="Morning"/>
<DeliveryPortOfCall Port="Athens" Date="2003-12-01" Time="Afternoon"/>
<Cargo>
<ContainerSet Size="Small" IsRefridgerated="true" Quantity="10"/>
<ContainerSet Size="Medium" IsRefridgerated="false" Quantity="2"/>
<ContainerSet Size="Large" IsRefridgerated="false" Quantity="3"/>
</Cargo>
</Manifest>
</BidRequest>
</payload>
</string_1>
</ns0:sayxmlhello>
</env:body>
</env:envelope>
|
Code Sample 27: SOAP Response
<?xml version="1.0" encoding="UTF-8"?>
<env:envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns0="http://www.examples.com/types">
<env:body>
<ns0:sayxmlhello>
<string_1><payload>
<BidRequest BidId="123654" DueDate="2004-05-01"
xmlns:n="http://bidservice.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="BidRequest.xsd">
<Company Name="Small Time Shipping" id="10001" url="www.BigTimeShipping.com"
phone="888-920-9817">
<Address Street="Small Ship Lane" City="Oceanana" State="MD" Zip="20819"/>
<BiddingAgent Name="Joe Bid Agent" Email="joe@shipping.com" Phone="888-890-1789"/>
</Company>
<Manifest>
<PickupPortOfCall Port="Miami" Date="2003-10-09" Time="Morning"/>
<DeliveryPortOfCall Port="Athens" Date="2003-12-01" Time="Afternoon"/>
<Cargo>
<ContainerSet Size="Small" IsRefridgerated="true" Quantity="10"/>
<ContainerSet Size="Medium" IsRefridgerated="false" Quantity="2"/>
<ContainerSet Size="Large" IsRefridgerated="false" Quantity="3"/>
</Cargo>
</Manifest>
</BidRequest></payload>
</string_1>
</ns0:sayxmlhello>
</env:body>
</env:envelope>
|
Consequences
Pros
- Simple to develop. From a developer's perspective, the interface is the same as a hello
world application that accepts and returns a string.
Cons
- Schema validation offered by the runtime cannot be used, and errors with the document
will not be picked up until the service has read the document in memory and attempted to process it.
Using Base64-Encoded or Raw Bytes in the SOAP Body
It is also possible to pass the XML document structure as a base64 Encoded string or as raw bytes in the body of the SOAP request.
The Base64 Content-Transfer-Encoding is a two way encoding scheme defined in RFC 1521 that is designed to represent arbitrary sequences of octets in a form that need not be humanly readable. It uses a 65-character subset of US-ASCII, enabling 6 bits to be represented per printable character (the extra 65th character, "=", is used to signify a special processing function). For example the string "A web service developed in JAX-RPC" is encoded into "QSBXZWIgU2VydmljZSBkZXZlbG9wZWQgaW4gSkFYLVJQQw==". The encoding and decoding algorithms are simple, but the encoded data are consistently only about 33 percent larger than the un-encoded data. This is the same encoding that is used by email applications when sending binary attachments, by web pages which accept file uploads, or by browsers when sending Basic Authentication credentials in HTTP headers.
Developers can choose to Base64 encode their own data, or send data that is encoded by external applications or programs. There are several open source implementations in Java of this encoding scheme that are available (for example, Apache and Sourceforge). Alternatively, if starting from WSDL, the data type xsd:base64binary can be used when describing the data type for the message. JAX-RPC automatically maps this to an array of bytes as per the standard mappings. With this technique, the runtime automatically encodes the raw XML data before sending it over the wire, and decodes it on the client side JAX-RPC runtime.
Example
The examples in Code Samples 28 and 29 show the endpoint interface and implementation for a service that contains two operations. The first accepts a Base64 encoded String and the second accepts raw bytes. The data for the string and the raw bytes is an XML document based on the XML schema for the purchase order (Code Sample 4). When the service is deployed, the array of bytes gets mapped to an xsd:base64binary data type as per the mappings defined in JAX-RPC, as can be seen in the WSDL (Code Sample 30), generated when the service is deployed.
Code Sample 28: Service Endpoint Interface
public interface IStringService extends Remote {
public String sayXMLHello(String xml) throws RemoteException;
public byte[] sayRawHello(byte[] xml) throws RemoteException;
}
|
Code Sample 29: Service Implementation
public class StringServiceImpl implements IStringService {
public String sayXMLHello(String xml){
byte[] data= Base64.decode( xml);
System.out.println(new String(data));
return xml;
}
public byte[] sayRawHello(byte[] xml) {
System.out.println(new String(xml));
return xml;
}
}
|
Code Sample 30: Generated WSDL File
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://www.examples.com/wsdl/StringService"
xmlns:ns2="http://www.examples.com/types"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
name="XMLStringService"
targetnamespace="http://www.examples.com/wsdl/StringService">
<types>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://www.examples.com/types"
xmlns:soap11-enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
targetnamespace="http://www.examples.com/types">
<import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<complextype name="sayRawHello">
<sequence>
<element name="arrayOfbyte_1" type="base64Binary" nillable="true"/>
</sequence>
</complextype>
<complextype name="sayRawHelloResponse">
<sequence>
<element name="result" type="base64Binary" nillable="true"/>
</sequence>
</complextype>
<complextype name="sayXMLHello">
<sequence>
<element name="String_1" type="string" nillable="true"/>
</sequence>
</complextype>
<complextype name="sayXMLHelloResponse">
<sequence>
<element name="result" type="string" nillable="true"/>
</sequence>
</complextype>
<element name="sayRawHello" type="tns:sayRawHello"/>
<element name="sayRawHelloResponse" type="tns:sayRawHelloResponse"/>
<element name="sayXMLHello" type="tns:sayXMLHello"/>
<element name="sayXMLHelloResponse" type="tns:sayXMLHelloResponse"/>
</schema>
</types>
<message name="IStringService_sayRawHello">
<part name="parameters" element="ns2:sayRawHello"/></message>
<message name="IStringService_sayRawHelloResponse">
<part name="result" element="ns2:sayRawHelloResponse"/></message>
<message name="IStringService_sayXMLHello">
<part name="parameters" element="ns2:sayXMLHello"/></message>
<message name="IStringService_sayXMLHelloResponse">
<part name="result" element="ns2:sayXMLHelloResponse"/></message>
<porttype name="IStringService">
<operation name="sayRawHello">
<input message="tns:IStringService_sayRawHello"/>
<output message="tns:IStringService_sayRawHelloResponse"/></operation>
<operation name="sayXMLHello">
<input message="tns:IStringService_sayXMLHello"/>
<output message="tns:IStringService_sayXMLHelloResponse"/></operation></porttype>
<binding name="IStringServiceBinding" type="tns:IStringService">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="sayRawHello">
<soap:operation soapaction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
<operation name="sayXMLHello">
<soap:operation soapaction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="XMLStringService">
<port name="IStringServicePort" binding="tns:IStringServiceBinding">
<soap:address xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
location="http://localhost:8080/base64/jaxrpc"/>
</port>
</service>
</definitions>
|
The client application (Code Sample 31) for this service invokes the two operations using XML data. It reads the XML stored in a file and sends it first as a as base64 encoded string and second as raw bytes. The response from the service is printed out to the console. Upon execution, this client sends two different messages (Code Samples 32 33) with identical data content in both the SOAP messages.
Code Sample 31: JAX-RPC Client Application
import com.examples.base64.clientbindings.*;
import com.examples.base64.Base64;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
public class WSDLClient {
public static void main(String[] args) throws Exception {
String url = "http://localhost:9090/base64/jaxrpc";
if (args.length == 1) {
url = args[0];
}
XMLStringService_Impl serviceproxy = new XMLStringService_Impl();
IStringService_Stub stub = ( IStringService_Stub)
(serviceproxy.getIStringServicePort());
stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY, url);
sendStringFromFile("bidrequest.xml", stub);
sendRawBytesFromFile("bidrequest.xml", stub);
}
/**
* Sends the contents to the service after reading from a file and
* base64 encoding the data
*/
public static void sendStringFromFile(String filename,
IStringService_Stub stub) throws Exception {
String filedata=readStringFromFileRaw(filename);
String base64encoded=encode(filedata);
SayXMLHello request = new SayXMLHello(base64encoded);
SayXMLHelloResponse response = stub.sayXMLHello(request);
String data= response.getResult();
System.out.println("Reply from server before decoding is : \n " + data);
byte[] raw=Base64.decode(data);
String answer=new String(raw);
System.out.println("\nAfter decoding , servers reply is : \n " + answer);
}
public static void sendRawBytesFromFile(String filename,
IStringService_Stub stub) throws Exception {
SayRawHello request = new
SayRawHello(readStringFromFileRaw(filename).getBytes());
SayRawHelloResponse response = stub.sayRawHello(request);
byte[] raw= response.getResult();
String answer=new String(raw);
System.out.println("Server replied back with following raw bytes : \n ");
for(int i=0;i<raw.length;i++)
System.out.print(raw[i]);
System.out.println("\nRaw bytes from server in string format are : \n " +
answer);
}
/**
* Reads the contents form a file and returns a String
*/
private static String readStringFromFileRaw(String filename) throws
Exception {
StringBuffer sb = new StringBuffer();
BufferedReader br = new BufferedReader(new InputStreamReader(new
FileInputStream(filename)));
String input = br.readLine();
while (input != null) {
sb.append(input);
input = br.readLine();
}
br.close();
String contents = sb.toString();
return contents;
}
private static String encode(String contents){
String data=Base64.encode(contents.getBytes());
return data;
}
}
|
Code Sample 32: SOAP Request with Developer-Encoded Base64 Data
<?xml version="1.0" encoding="UTF-8"?>
<env:envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:ns0="http://www.examples.com/types">
<env:body>
<ns0:sayxmlhello>
<string_1>PHBheWxvYWQ+CTxCaWRSZXF1ZXN0IEJpZElkPSIxMjM2NTQiIER1ZURhdGU9Ij
IwMDQtMDUtMDEiIHhtbG5zOm49Imh0dHA6Ly9iaWRzZXJ2aWNlLmNvbSIgeG1sbnM6eHNpPS
JodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOm5vTmFtZX
NwYWNlU2NoZW1hTG9jYXRpb249IkJpZFJlcXVlc3QueHNkIj4JCTxDb21wYW55IE5hbWU9Il
NtYWxsIFRpbWUgU2hpcHBpbmciIGlkPSIxMDAwMSIgdXJsPSJ3d3cuQmlnVGltZVNoaXBwaW
5nLmNvbSIgcGhvbmU9Ijg4OC05MjAtOTgxNyI+CQkJPEFkZHJlc3MgU3RyZWV0PSJTbWFsbC
BTaGlwIExhbmUiIENpdHk9Ik9jZWFuYW5hIiBTdGF0ZT0iTUQiIFppcD0iMjA4MTkiLz4JCQ
k8QmlkZGluZ0FnZW50IE5hbWU9IkpvZSBCaWQgQWdlbnQiIEVtYWlsPSJqb2VAc2hpcHBpbm
cuY29tIiBQaG9uZT0iODg4LTg5MC0xNzg5Ii8+CQk8L0NvbXBhbnk+CQk8TWFuaWZlc3Q+CQ
kJPFBpY2t1cFBvcnRPZkNhbGwgUG9ydD0iTWlhbWkiIERhdGU9IjIwMDMtMTAtMDkiIFRpbW
U9Ik1vcm5pbmciLz4JCQk8RGVsaXZlcnlQb3J0T2ZDYWxsIFBvcnQ9IkF0aGVucyIgRGF0ZT
0iMjAwMy0xMi0wMSIgVGltZT0iQWZ0ZXJub29uIi8+CQkJPENhcmdvPgkJCQk8Q29udGFpbm
VyU2V0IFNpemU9IlNtYWxsIiBJc1JlZnJpZGdlcmF0ZWQ9InRydWUiIFF1YW50aXR5PSIxMC
IvPgkJCQk8Q29udGFpbmVyU2V0IFNpemU9Ik1lZGl1bSIgSXNSZWZyaWRnZXJhdGVkPSJmYW
xzZSIgUXVhbnRpdHk9IjIiLz4JCQkJPENvbnRhaW5lclNldCBTaXplPSJMYXJnZSIgSXNSZW
ZyaWRnZXJhdGVkPSJmYWxzZSIgUXVhbnRpdHk9IjMiLz4JCQk8L0NhcmdvPgkJPC9NYW5pZm
VzdD4JPC9CaWRSZXF1ZXN0PjwvcGF5bG9hZD4=
</string_1>
</ns0:sayxmlhello>
</env:body>
</env:envelope>
|
Code Sample 33: SOAP Request with xsd:base64binary Data Type
<?xml version="1.0" encoding="UTF-8"?>
<env:envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:ns0="http://www.examples.com/types">
<env:body>
<ns0:sayrawhello>
<arrayofbyte_1>PHBheWxvYWQ+CTxCaWRSZXF1ZXN0IEJpZElkPSIxMjM2NTQiIER1ZURhdGU9
IjIwMDQtMDUtMDEiIHhtbG5zOm49Imh0dHA6Ly9iaWRzZXJ2aWNlLmNvbSIgeG1sbnM6eHNpPSJodHRwO
i8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOm5vTmFtZXNwYWNlU2NoZW1hTG
9jYXRpb249IkJpZFJlcXVlc3QueHNkIj4JCTxDb21wYW55IE5hbWU9IlNtYWxsIFRpbWUgU2hpcHBpbmc
iIGlkPSIxMDAwMSIgdXJsPSJ3d3cuQmlnVGltZVNoaXBwaW5nLmNvbSIgcGhvbmU9Ijg4OC05MjAtOTgx
NyI+CQkJPEFkZHJlc3MgU3RyZWV0PSJTbWFsbCBTaGlwIExhbmUiIENpdHk9Ik9jZWFuYW5hIiBTdGF0Z
T0iTUQiIFppcD0iMjA4MTkiLz4JCQk8QmlkZGluZ0FnZW50IE5hbWU9IkpvZSBCaWQgQWdlbnQiIEVtYW
lsPSJqb2VAc2hpcHBpbmcuY29tIiBQaG9uZT0iODg4LTg5MC0xNzg5Ii8+CQk8L0NvbXBhbnk+CQk8TWF
uaWZlc3Q+CQkJPFBpY2t1cFBvcnRPZkNhbGwgUG9ydD0iTWlhbWkiIERhdGU9IjIwMDMtMTAtMDkiIFRp
bWU9Ik1vcm5pbmciLz4JCQk8RGVsaXZlcnlQb3J0T2ZDYWxsIFBvcnQ9IkF0aGVucyIgRGF0ZT0iMjAwM
y0xMi0wMSIgVGltZT0iQWZ0ZXJub29uIi8+CQkJPENhcmdvPgkJCQk8Q29udGFpbmVyU2V0IFNpemU9Il
NtYWxsIiBJc1JlZnJpZGdlcmF0ZWQ9InRydWUiIFF1YW50aXR5PSIxMCIvPgkJCQk8Q29udGFpbmVyU2V
0IFNpemU9Ik1lZGl1bSIgSXNSZWZyaWRnZXJhdGVkPSJmYWxzZSIgUXVhbnRpdHk9IjIiLz4JCQkJPENv
bnRhaW5lclNldCBTaXplPSJMYXJnZSIgSXNSZWZyaWRnZXJhdGVkPSJmYWxzZSIgUXVhbnRpdHk9IjMiL
z4JCQk8L0NhcmdvPgkJPC9NYW5pZmVzdD4JPC9CaWRSZXF1ZXN0PjwvcGF5bG9hZD4=
</arrayofbyte_1>
</ns0:sayrawhello>
</env:body>
</env:envelope>
|
Consequences
Pros
- This may be useful when the XML contains characters or declarations that are not
supported either by the SOAP message infoset or by the runtime
implementation. Examples of these are DTD declarations and locale-specific character encoding.
Cons
- Interoperability. Both parties need to know out of band what the data is.
- Increased message size due to the Base64 algorithm (increases size by about 33%).
Switch-Off Data Binding
A JAX-RPC endpoint attempts to bind the XML data types in the request to appropriate Java data types as per the standard XML-Java mappings defined in the specifications when it receives the SOAP request conforming to the WSDL service description. The JAX-RPC implementation can be configured to switch off data binding for the literal encoding. This configuration allows the method parameters in the generated interfaces to be bound to javax.xml.soap.SOAPElement rather than the corresponding data types. This can be useful when an application wants to use a custom binding framework like JAXB instead of using the JAX-RPC bindings, or wants to consume the XML representation of the service.
Example
The example below starts with a WSDL (Code Sample 8) that describes the service and is used to generate the service endpoint interface and implementation using the wscompile and wsdeploy tools. Code Sample 34 shows the extract from the Ant build file which specifies the –f:nodatabinding option for wscompile. This results in the schema elements being bound to a javax.xml.soap.SOAPElement for the methods in the corresponding generated endpoint interface shown in Code Sample 35.
Code Sample 34: Server-Side Ant Target Specifying Nodatabinding
<!-- Generate the server bindings from the code -->
<target name="generate-server-from-wsdl" depends="init"
description="Runs wscompile to generate the client stub classes">
<echo message="Running wscompile...."/>
<exec executable="${wscompile}">
<arg line="-gen:server"/>
<arg line="-f:documentliteral"/>
<arg line="-f:nodatabinding"/>
<arg line="-model ${buildhome}/${appname}/model.gz"/>
<arg line="-f:wsi"/>
<arg line="-d ${buildhome}/${appname}/classes/server"/>
<arg line="-classpath ${buildhome}/${appname}/classes/server"/>
<arg line="${config.wsdl}"/>
<arg line="-keep"/>
</exec>
</target>
|
Code Sample 35: Generated Endpoint Interface
// This class was generated by the JAXRPC SI, do not edit.
// Contents subject to change without notice.
// JAX-RPC Standard Implementation (1.1_03, build R65)
package com.examples.nodatabinding;
public interface IPurchaseOrder extends java.rmi.Remote {
public javax.xml.soap.SOAPElement acceptPO(javax.xml.soap.SOAPElement parameters)
throws com.examples.nodatabinding.POProcessingProblem, java.rmi.RemoteException;
}
|
Once the interface is generated, the implementation class can be written and service deployed using wsdeploy. Code Sample 36 shows the code for this class, which contains a simple utility method to print out the contents to the console.
Code Sample 36: Service Implementation Class
package com.examples.nodatabinding;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.soap.*;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.rpc.server.ServletEndpointContext;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.rmi.RemoteException;
import javax.xml.rpc.server.ServiceLifecycle;
public class PurchaseOrderService implements IPurchaseOrder, ServiceLifecycle {
private ServletEndpointContext context;
public void init(Object context) {
this.context = (ServletEndpointContext) context;
}
public void destroy() {
}
public SOAPElement acceptPO(SOAPElement payload) throws
POProcessingProblem, RemoteException {
printNodeToConsole(payload);
try {
SOAPElement response = createSOAPMessage("/Response.xml");
return response;
} catch (Exception e) {
System.out.println("Exception in creating a response" + e);
throw new POProcessingProblem(createExceptionXML(e.getMessage()));
}
}
public static void printNodeToConsole(Node n) {
try {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
// also print to console
transformer.transform(new DOMSource(n), new StreamResult(System.out));
} catch (Exception e) {
System.out.println(e);
}
}
public SOAPElement createSOAPMessage(String filename) throws Exception {
SOAPMessage message = MessageFactory.newInstance().createMessage();
SOAPPart soap = message.getSOAPPart();
SOAPEnvelope envelope = soap.getEnvelope();
SOAPBody body = envelope.getBody();
Document doc = readFileCreateDocument(filename);
if (doc == null)
throw new IOException("Problem reading and creating DOM from
file,readFileCreateDocument() returned null ");
SOAPElement element = body.addDocument(doc);
return element;
}
/**
* Read a file and create a DOM out of it
* @param filename the file where the XML is stored
* @return
*/
private Document readFileCreateDocument(String filename) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(true);
DocumentBuilder parser = factory.newDocumentBuilder();
InputStream in =
context.getServletContext().getResourceAsStream(filename);
Document xml = parser.parse(in);
in.close();
return xml;
}
public static SOAPElement createExceptionXML(String msg) {
try {
SOAPFactory sf = SOAPFactory.newInstance();
SOAPElement se = sf.createElement("message");
se.addTextNode(msg);
return se;
} catch (Exception se) {
// should never be called
System.out.println("Exception creating Exception !!" + se);
return null;
}
}
}
|
When generating the client-side bindings, the Ant target is similar to the one in Code Sample 34. This also contains the –f:nodatabinding option so that the client side interfaces and stubs that are generated from the WSDL also contain a binding to a SOAPElement. This is used to demonstrate how the client side code (Code Sample 38) can read the contents of an XML file and generate a corresponding SOAPElement object from it. This would be useful if the client was another J2EE application that needed to handle or parse the documents in their entirety.
It is important to note that switching off data binding on the client is not required, just because it has been done on the server side. There is no dependence between the implementation techniques for the service and the client. One can still generate a client using the WSDL as described earlier (Code Sample 13), since the WSDL is identical, and not care about data binding on the client side. In fact, that client can be used here directly, and is also included in the <Nodatabindingexample/src/client> location.
Code Sample 37: Client Side Ant Target Specifying Nodatabinding
<!-- Generate the client stubs code from the WSDL -->
<target name="generate-client-from-wsdl" depends="init"
description="Runs wscompile to generate the client stub classes">
<echo message="Running wscompile...."/>
<exec executable="${wscompile}">
<arg line="-f:wsi"/>
<arg line="-f:nodatabinding"/>
<arg line="-gen:client"/>
<arg line="-d ${buildhome}/${appname}/classes/client"/>
<arg line="${config.wsdl}"/>
<arg line="-keep"/>
</exec>
</target>
|
Code Sample 38: Client Application Without Data Binding
public class NoBindingClient {
public static void main(String[] args) throws Exception {
String url = "http://localhost:9090/nodatabinding/jaxrpc";
if (args.length == 1) {
url = args[0];
}
POService_Impl serviceproxy = new POService_Impl();
IPurchaseOrder_Stub stub = (IPurchaseOrder_Stub) (serviceproxy.getIPurchaseOrderPort());
stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY, url);
SOAPElement payload = createSOAPMessage("purchaseorder.xml");
SOAPElement response = stub.acceptPO(payload);
printNodeToConsole(response);
}
// other methods. See NoBindingClient.java
}
|
Strategy: Integration with JAXB
As mentioned earlier, one scenario where not using data binding can be useful is when a custom binding framework like JAXB is used.
 |
|
Figure 7: JAXB Use in a JAX-RPC Endpoint
|
JAXB provides an API and tools that automate the mapping between XML documents and Java objects by compiling an XML schema into one or more Java classes. The combination of the schema-derived classes and the binding framework enable developers to perform the following operations on an XML document:
- Un-marshall XML content into a Java representation
- Access, update and validate the Java representation against schema constraints
- Marshal the Java representation of the XML content into XML content
The JAX-RPC endpoint implementation can use the JAXB API to handle the binding between Java and XML. If the incoming XML document conforms to a schema, the schema can be compiled by a JAXB schema compiler tool into its Java representation using data type mappings standardized by the JAXB specifications. The JAX-RPC endpoint implementation can then directly be un-marshalled and marshalled by any document that conforms to this schema, to and from its object representation in Java by using the JAXB API.
The no-data binding design can be used to obtain access the document as a SOAPElement and then leverage the JAXB framework in the above described fashion. In order for this strategy to work, it is important to understand how objects can be marshalled and un-marshalled from a SOAPElement representation using the JAXB API which essentially deals with DOM Nodes. The relationship between the SAAJ SOAPElement and the DOM interfaces is shown in Figure 8.
 |
|
Figure 8: Relationship Between SOAPElement and DOM Interfaces
|
SOAPElement implements the Node interface, JAXB supports marshalling to a DOMResult which wraps a Node. Thus, a dummy SOAPElement can be created, the object marshalled to it, and the contents accessed as shown below.
SOAPElement parent = SOAPFactory.newInstance().createElement("dummy");
m.marshal( status, new DOMResult(parent) );
return (SOAPElement)parent.getChildElements().next();
|
The above code creates a dummy SOAPElement (<dummy></dummy>), marshals the object into this, and accesses the first child. The end result is that the SOAPElement returned by the above code contains the XML inside the dummy element.
<dummy>
<ns0:status xmlns="http://www.examples.com/types"
xsi:schemalocation="http://www.examples.com/types PurchaseOrder.xsd">
<orderid xmlns="">ABC1080440067456</orderid>
<timestamp xmlns="">Sat Mar 27 21:14:27 EST 2004</timestamp>
</ns0:status>
</dummy>
|
Code Sample 39 shows the code for the JAX-RPC service implementation class that uses this no-data binding design along with the JAXB API. The service accepts purchase orders conforming to the schema in Code Sample 4, and
returns responses conforming to schemas in Code Samples 5 and 6.
Code Sample 39: Integration of JAX into a JAX-RPC Service Implementation
package com.examples.nodatabinding;
import com.examples.nodatabinding.po.PurchaseOrder;
import com.examples.nodatabinding.po.LineItem;
import com.examples.nodatabinding.postatus.ObjectFactory;
import com.examples.nodatabinding.postatus.PurchaseOrderStatus;
import com.examples.nodatabinding.postatus.Status;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.soap.*;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamResult;
import java.rmi.RemoteException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.io.IOException;
public class PurchaseOrderService implements IPurchaseOrder {
public SOAPElement acceptPO(SOAPElement payload) throws POProcessingProblem, RemoteException {
try {
PurchaseOrder order = toObject(payload);
System.out.println("JAXB Unmarshalled the purchase Order ");
System.out.println("PurchaseOrder received in the Web service");
System.out.println("Order ID " + order.getPoID());
System.out.println("Order creation date " + order.getCreateDate());
System.out.println("Ship To Address " + order.getShipTo());
System.out.println("Bill To Address " + order.getBillTo());
System.out.println("Order Contents :");
List items = order.getItems();
Iterator it = items.iterator();
while (it.hasNext()) {
LineItem item = (LineItem) it.next();
System.out.println("Item Name =" + item.getItemname() +
"Price=" + item.getPrice() +
"Quantity=" + item.getQuantity());
}
SOAPElement response = fromObject();
return response;
} catch (Exception e) {
System.out.println("Exception in creating a response" + e);
throw new POProcessingProblem(createExceptionXML(e.getMessage()));
}
}
/**
* Unmarshall the element in the form of a SOAPElement to its JAXB object representation
*/
public PurchaseOrder toObject(Node n) throws Exception {
printNodeToConsole(n);
JAXBContext jc = JAXBContext.newInstance("com.examples.nodatabinding.po");
Unmarshaller u = jc.createUnmarshaller();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Object o = u.unmarshal(n);
// use the object that is generated by JAXB
PurchaseOrder order = (PurchaseOrder) o;
return order;
}
/**
* Creates a SOAPElement from an objects
*/
public SOAPElement fromObject() throws Exception {
// creating the ObjectFactory.
ObjectFactory objFactory = new ObjectFactory();
Status status = objFactory.createStatus();
status.setTimestamp(new Date().toString());
status.setOrderid("ABC" + System.currentTimeMillis());
JAXBContext jc =
JAXBContext.newInstance("com.examples.nodatabinding.postatus");
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.setProperty(Marshaller.JAXB_SCHEMA_LOCATION,
"http://www.examples.com/types PurchaseOrder.xsd");
SOAPElement parent = SOAPFactory.newInstance().createElement("dummy");
m.marshal(status, new DOMResult(parent));
return (SOAPElement) parent.getChildElements().next();
}
public static void printNodeToConsole(Node n) {
try {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
// also print to console
transformer.transform(new DOMSource(n), new StreamResult(System.out));
} catch (Exception e) {
System.out.println(e);
}
}
public static SOAPElement createExceptionXML(String msg) {
try {
SOAPFactory sf = SOAPFactory.newInstance();
SOAPElement se = sf.createElement("message");
se.addTextNode(msg);
return se;
} catch (Exception se) {
// should never be called
System.out.println("Exception creating Exception !!" + se);
return null;
}
}
}
|
The complete example can be found in the <Nodatabinding-JAXB-Integration> directory. The build script for this example contains two important targets. One invokes the xjc compiler and generates the JAXB binding, the second packages these classes and the necessary JAXB properties in the WAR file. The schemas are also packaged in the WAR in case the client needs to reference them. These extracts are shown in Code Sample 40.
Code Sample 40: Extract of Build File Showing Integration with JAXB
<!-- Generate the JAXB binding for the schema -->
<target name="create-value-objects">
<exec executable="${xjc}">
<arg line="-p com.examples.nodatabinding.po"/>
<arg line="-d src/server"/>
<arg line="./configs/PurchaseOrder.xsd"/>
</exec>
<exec executable="${xjc}">
<arg line="-p com.examples.nodatabinding.postatus"/>
<arg line="-d src/server"/>
<arg line="./configs/PurchaseOrderStatus.xsd"/>
</exec>
<exec executable="${xjc}">
<arg line="-p com.examples.nodatabinding.poroblem"/>
<arg line="-d src/server"/>
<arg line="./configs/POProcessingProblem.xsd"/>
</exec>
</target>
<!-- Create the portable WAR FILE. This is the standard J2EE WAR file-->
<target name="create-war" depends="generate-server-from-wsdl,compile-server">
<war warfile="${buildhome}/${appname}/jaxrpc-${appname}-raw.war"
webxml="${webapp.webxml}">
<!-- Include the schema's for the Purcahseorder and status so that the
client has access to them -->
<fileset dir="${basedir}/configs/">
<include name="**/*.xsd"/>
</fileset>
<webinf dir="${basedir}/configs/" includes="POService.wsdl,jaxrpc-ri.xml"/>
<webinf dir="${buildhome}/${appname}/" includes="model.gz"/>
<webinf dir="${basedir}/configs/" includes="${webapp.jaxrpc.file}"
defaultexcludes="no"/>
<classes dir="${buildhome}/${appname}/classes/server" includes="**/*.class"
defaultexcludes="no"/>
<!-- Include everything relevant to JAXB like properties etc, omit the sources -->
<classes dir="${basedir}/src/server" excludes="**/*.java" defaultexcludes="no"/>
</war>
</target>
|
The Java value types can be generated from the schema for the business document using a binding framework like JAXB. The service endpoint and its implementation can then be written to use these generated classes as value types and deployed in a web container.
There are several issues with an approach that uses the generated classes directly as value objects or value types. First, the JAXB framework generates a set of interfaces for the XML schema types. JAX-RPC requires that the Java value types be valid Java classes, with a default constructor. So the generating binding cannot be used directly. This leaves the option of either:
- Writing wrapper objects for each of the generated binding classes and creating
another abstraction layer on top. This approach will run into issues
when dealing with complex object relationships (like one to many).
- Using the JAXB-generated implementation classes for the generated Java types
directly. The schema compiler will generate two sets of files :
- Interfaces that
correspond to the XML elements as per the mapping rules defined in
JAXB (
PurchaseOrder.java, Address.java) in a
com.examples.docliteral package
- Implementation classes for the interfaces which are specific to the JAXB runtime.
(
PurchaseOrderImpl.java, AddressImpl.java) in a
com.examples.docliteral.impl package
By using the implementation classes, the service endpoint will be tied to the underlying JAXB implementation. Also, these generated classes cannot be modified, since changes will be lost upon recompilation of the source schema. There is also the issue of the classes generated on the client side, and how the client will use dependent objects. In short, neither of these two approaches is clean. The example code provided in the <Bad-JAXB-Integration> directory uses this approach, but is only meant to demonstrate the issues that developers will face with such a strategy.
Consequences
Pros
- Integration with data binding APIs like JAXB that are optimized for parsing the XML documents
Cons
- Behavior is specific to the runtime. For example, Java WSDP allows a
–nodatabinding
switch. Other implementations like those packaged with J2EE application servers may not support such an option with their tools.
[Page 1] [Page 2] [Page 3] [Page 4] [Page 5] [Page 6]
|