Table of Contents
This chapter describes several data type issues related to JAX-RPC web services.
This chapter describes the standard Java/XML data type mapping and JAX-RPC value type, which are used as web service parameters in a user-defined class. Next, it will describe the JAX-RPC holder classes for out or in/out parameters as well as how to send error messages to the web service clients.
This section describes several issues concerning the data types used for the web service operations and clients.
The following are the features of web service data types.
The JAX-RPC value type is a type that is passed by value in a web service request or response. The JAX-RPC value type is typically represented as a user-defined JavaBeans component. JEUS web services provide the capability to use user-defined JavaBeans as a parameter and return type of a web service operation.
Using holders as in/out parameter
The in/out parameter is the parameter of a web service operation that is used as input and output. The in/out parameter can be used when the web service operation needs to return multiple outputs. JEUS web services support the in/out parameters by using the standard JAX-RPC holder classes.
So far, web service operation examples in this chapter only used one type (string) for both parameter and return types. JEUS web services map Java types to XML/WSDL definitions. For example, a JEUS web service maps the java.lang.String class to the xsd:string XML data type.
This section describes the data types supported by JEUS web services, and the requirements that a type must meet in order to be supported by JEUS web services.
Application developers do not need to know the details of these mappings, but they must note that not all Java classes can be used as a parameter or return type of a method.
The following table describes the Java and XML data type mappings built into JEUS web services.
[Table 25.1] Built-in XML/Java Type Mapping
XML Data Type | Java Data Type |
---|---|
xsd:string | java.lang.String |
xsd:boolean | boolean, java.lang.Boolean * |
xsd:double | double, java.lang.Double * |
xsd:float | float, java.lang.Float * |
xsd:int | int, java.lang.Integer |
xsd:integer | java.math.BigInteger |
xsd:long | long, java.lang.Long * |
xsd:short | short, java.lang.Short * |
xsd:byte | byte, java.lang.Byte |
xsd:Decimal | java.math.BigDecimal |
xsd:base64Binary | byte[ ] |
xsd:hexBinary | byte[ ] |
xsd:QName | javax.xml.rpc.namespace.QName |
xsd:dateTime | java.util.Calendar |
xsd:gYearMonth | java.util.Calendar |
xsd:gYear | java.util.Calendar |
xsd:gMonthDay | java.util.Calendar |
xsd:anyURI | java.net.URI (JDK 1.4 or over) / java.lang.String (JDK 1.4) |
xsd:duration | java.lang.String |
xsd:name | java.lang.String |
xsd:NCName | java.lang.String |
xsd:NMTOKEN | java.lang.String |
xsd:nomalizedString | java.lang.String |
xsd:time | java.util.Calendar |
xsd:token | java.lang.String |
xsd:unsignedByte | short |
xsd:unsignedLong | java.math.BigInteger |
xsd:unsignedInt | long |
xsd:unsignedShort | int |
SOAP-ENC:base64 | byte[ ] |
SOAP-ENC:string | java.lang.String |
SOAP-ENC:boolean | boolean, java.lang.Boolean * |
SOAP-ENC:double | double, java.lang.Double * |
SOAP-ENC:float | float, java.lang.Float * |
SOAP-ENC:int | int, java.lang.Integer * |
SOAP-ENC:long | long, java.lang.Long * |
SOAP-ENC:short | short, java.lang.Short * |
SOAP-ENC:byte | byte, java.lang.Byte * |
SOAP-ENC:interger | java.math.BigInteger |
SOAP-ENC:decimal | java.math.BigDecimal |
SOAP-ENC:Array | java.math.BigDecimal |
1. The prefix xsd represents this XML namespace URI: (http://www.w3.org/2001/XMLSchema).
2. The prefix SOAP-EN represents this XML namespace URI: (http://schemas.xmlsoap.org/soap/encoding).
3. If the WSDL specifies that an object can be set to null, the caller can use 'xsd:nil' when sending or receiving data. Java primitive data types are replaced by their wrapper classes. In the previous table, the Java types followed by an asterisk (*) represent this.
The QName of the XML type can be used to specify a data type in a DII client
String NS_XSD = “http://www.w3.org/2001/XMLSchema”; String XSD_DATETIME = new QName(NS_XSD, “dateTime”); call.addParameter(“arg1”, XSD_DATETIME, PARAM_MODE_IN);
JEUS web services also support arrays that are defined by the JAX-RPC types. For example, int[ ], String[ ], and multidimensional arrays, such as java.math.BigDecimal[ ][ ], are supported.
JEUS web services also support any user-defined types implemented in an application. The JAX-RPC specification refers to such classes as 'value types'. To be supported by JEUS web services, user-defined classes must conform to the following rules.
It must have a public default no-argument constructor.
It must not implement (either directly or indirectly) the java.rmi.Remote interface.
The field types must be supported by JEUS web services.
The class may contain public, private, or protected fields. To pass a field value during a web service call, the field must meet the following conditions.
A public field cannot be final or transient.
A non-public field must have the corresponding getter and setter methods.
JavaBeans components, which conform to these rules, are also supported.
The user-defined type that conforms to the rules stated in "25.2.3. User Defined Types: JAX-RPC Value Type" of this manual, can be used as parameter or return type of a JEUS web service. Such user-defined type is called a JAX-RPC value type.
This section describes an example of using the JAX-RPC value type. The example 'CalcService' is a calculator that takes two numbers and an operator and returns the result as a number. A web service client will be created for the CalcService web service.
The following is the source code for the CalcService class, ‘Calculator.java’.
[Example 25.1] <<Calculator.java>>
package calc; public class Calculator implements CalculatorIF { public Calculator() { } public double calc(CalcData data) { String op = data.getOp(); double num1 = data.getNum1(); double num2 = data.getNum2(); double ret = -9999.0; if (op.equals("plus")) { ret = num1 + num2; } else if (op.equals("minus")) { ret = num1 - num2; } else if (op.equals("mult")) { ret = num1 * num2; } else if (op.equals("div")) { if (num2 != 0) ret = num1 / num2; } return ret; } }
The calc( ) method takes an argument of the CalcData type
and returns '–9999.0' when an error occurs. A more detailed error
handling procedures will be described later.
The following is the source code of CalcData.java.
[Example 25.2] <<CalcData.java>>
package calc; public class CalcData { private double num1; private double num2; private String op; public CalcData() { } public double getNum1() { return num1; } public double getNum2() { return num2; } public String getOp() { return op; } public void setNum1(double n) { num1 = n; } public void setNum2(double n) { num2 = n; } public void setOp(String s) { op = s; } }
The CalcData class meets the requirements of a JAX-RPC value
type. This means that an instance of the CalcData class can be copied on
the fly by value.
The following is the service endpoint interface file, 'CalculatorIF.java'.
[Example 25.3] <<CalculatorIF.java>>
package calc; import java.rmi.Remote; import java.rmi.RemoteException; public interface CalculatorIF extends Remote { public double calc(CalcData data) throws RemoteException; }
Execute the following command to compile the source code and generate web service artifacts.
ant compile
This command generates the class files under the build directory.
Execute the following command to create a deployable EAR file.
ant wsear
Now, deploy the created web service module. The following is the web service URL.
http://localhost:8088/Calculator1Service/Calculator1Service?wsdl
The following command generates a Proxy client for the 'CalcService' web service.
ant wsdl2java
Assume that the package name of the generated stub code is 'com.test.calc'.
The following is the source code for the client program.
[Example 25.4] <<CalcClient.java>>
import com.test.calc.*; import javax.xml.rpc.soap.SOAPFaultException; public class CalcClient { public static void main(String[] args) { CalcClient calc = new CalcClient(); if (args.length != 3) { System.out.println("usage: java CalcClient num1 op num2"); System.out.println(" where op is one of " + "'plus', 'minus', 'mult', 'div'"); System.exit(1); } try { calc.run(args); } catch (SOAPFaultException e) { System.err.println("faultcode = " + e.getFaultCode()); System.err.println("faultString = " + e.getFaultString()); } catch (Exception e) { System.err.println(e.toString()); e.printStackTrace(); } } public void run(String[] args) throws Exception { CalculatorIF port = new Calculator1Service_Impl().getCalculatorIFPort(); CalcData data = new CalcData(); data.setNum1((new Double(args[0])).doubleValue()); data.setNum2((new Double(args[2])).doubleValue()); data.setOp(args[1]); double ret = port.calc(data); System.out.println(ret); } }
Once a client code has been implemented, you can compile the
client and stub codes by using the following command
ant build
Execute Ant task to execute the client
ant runclient
If executed successfully, the following result will be displayed.
2.0
If a web service operation needs to return multiple values, the return value can be defined as a JAX-RPC value type, or one or more out or in/out parameters can be specified. The holder class is a helper class that can be used as an in/out parameter.
JEUS web services provide JAX-RPC holder classes for simple data types. The standard holder classes provided by JAX-RPC are shown in the following table.
[Table 25.2] Built-in Holder Class
Use the value field of each holder class to access its data. The following is the modified source code for 'Caculator.java'.
[Example 25.5] << Calculator.java >>
package calc; import javax.xml.rpc.holders.DoubleHolder; public class Calculator implements CalculatorIF { public Calculator() { } public void calc(CalcData data, DoubleHolder result){ String op = data.getOp(); double num1 = data.getNum1(); double num2 = data.getNum2(); double ret = -9999.0; if (op.equals("plus")) { ret = num1 + num2; } else if (op.equals("minus")) { ret = num1 - num2; } else if (op.equals("mult")) { ret = num1 * num2; } else if (op.equals("div")) { if (num2 != 0) ret = num1 / num2; } result.value = ret; } }
The source code imports the built-in holder class,
javax.xml.rpc.holders.Double Holder.
import javax.xml.rpc.holders.DoubleHolder;
The signature of calc( ) method has also been modified. Its return type is void, and it accepts holder object as its second parameter.
public void calc(CalcData data, DoubleHolder result){ . . . }
To access the value of the holder object, use the value field of the holder class.
result.value = ret;
The web service must be packaged and deployed before creating a web service client for the service. The client program, 'CalcClient.java', must also be modified.
The following shows the run( ) method in the client source code, 'CalcClient.java'.
[Example 25.6] << run( ) Method in CalcClient.java >>
public void run(String[] args) throws Exception { CalculatorIF port = new CalcService_Impl().getCalculatorIFPort(); CalcData data = new CalcData(); data.setNum1((new Double(args[0])).doubleValue()); data.setNum2((new Double(args[2])).doubleValue()); data.setOp(args[1]); DoubleHolder ret = new DoubleHolder(); port.calc(data, ret); System.out.println(ret.value); }
Make sure to re-generate the proxy source code before compiling 'CalcClient.java'.
The following are the steps for creating a holder class for the user-defined class.
Implement the javax.xml.rpc.holders.Holder interface.
Name your class TypeHolder, where Type is the name of the class that the holder object holds. For example, a holder class for the CalcData class would be 'CalcDataHolder'.
Declare your holder class as public.
Create a public field called 'value', whose data type is the same as the Holder Class type.
Create a default no-argument constructor that initializes the 'value' field to a default value.
Create a constructor that sets the 'value' field to the value of the constructor's parameter.
The CalcService example will be modified to illustrate how to use a holder class of the JAX-RPC value type. This is the CalcData class that includes the result value as its member variable. A private member variable called 'result' and its getter/setter methods are added to the following class.
[Example 25.7] << CalcData.java >>
package calc; public class CalcData { private double num1; private double num2; private String op; private double result; public CalcData() { } public double getNum1() { return num1; } public double getNum2() { return num2; } public String getOp() { return op; } public double getResult() { return result; } public void setNum1(double n) { num1 = n; } public void setNum2(double n) { num2 = n; } public void setOp(String s) { op = s; } public void setResult(double n) { result = n; } }
The holder class of the CalcData class is called
CalcDataHolder. The CalcDataHolder class has a public 'value' field
whose data type is CalcData and two constructors that initialize the
'value' field.
[Example 25.8] << CalcDataHolder.java >>
package calc; import javax.xml.rpc.holders.Holder; public class CalcDataHolder implements Holder { public CalcData value; public CalcDataHolder() { } public CalcDataHolder(CalcData value) { this.value = value; } }
The following is the source code in 'Calculator.java' that
uses the holder class. The calc( ) method defines a parameter whose data
type is CalcDataHolder. The public 'value' field is used to access the
object that the holder object holds.
package calc; public class Calculator implements CalculatorIF { public Calculator() { } public void calc(CalcDataHolder calcData) { CalcData data = calcData.value; String op = data.getOp(); double num1 = data.getNum1(); double num2 = data.getNum2(); double ret = -9999.0; if (op.equals("plus")) { ret = num1 + num2; } else if (op.equals("minus")) { ret = num1 - num2; } else if (op.equals("mult")) { ret = num1 * num2; } else if (op.equals("div")) { if (num2 != 0) ret = num1 / num2; } data.setResult(ret); // The following line is not necessary in this case. // But we will use it to show the usage of holder class. calcData.value = data; } }
The following is the client source code. The 'ant wsdl2java' command also generates holder classes for the client from WSDL document. Note that the holder classes generated in this way can be different from the one that is manually created.
import com.test.calc.*; // generated by ant process-wsdl import com.test.calc.holders.*; // generated by ant process-wsdl public class CalcClient { public static void main(String[] args) { CalcClient calc = new CalcClient(); if (args.length != 3) { System.out.println("usage: java CalcClient num1 op num2"); System.out.println(" where op is one of " + "'plus', 'minus', 'mult', 'div'"); System.exit(1); } try { calc.run(args); } catch (Exception e) { System.err.println(e.toString()); e.printStackTrace(); } } public void run(String[] args) throws Exception { CalculatorIF port = new Calculator3Service_Impl().getCalculatorIFPort(); CalcData data = new CalcData(); CalcDataHolder dataHolder = new CalcDataHolder(data); data.setNum1((new Double(args[0])).doubleValue()); data.setNum2((new Double(args[2])).doubleValue()); data.setOp(args[1]); port.calc(dataHolder); System.out.println(dataHolder.value.getResult()); } }
In JEUS web services, you can use the java.rmi.RemoteException or one that inherits the java.lang.Exception class as a SOAP fault. The SOAP fault is used to send error and/or status information through a SOAP Message. For more detailed information, refer to the '4.4 SOAP Fault' section of the SOAP 1.1 specification.
When an exception status needs to be sent to the web service client in the web service application, the web service operation can be implemented to throw a java.rmi.RemoteException or a user-defined exception. The RemotException that is thrown is automatically wrapped as a SOAP Fault and delivered to the web service client inside the SOAP message body.
In the CalcService example, set the return value to '–9999.0' when an error occurs as in the following.
[Example 25.9] << Calculator.java >>
package calc; import java.rmi.RemoteException; public class Calculator { public Calculator() { } public double calc(CalcData data) throws RemoteException, DevideByZeroException { String op = data.getOp(); double num1 = data.getNum1(); double num2 = data.getNum2(); double ret = 0; if (op.equals("plus")) { ret = num1 + num2; } else if (op.equals("minus")) { ret = num1 - num2; } else if (op.equals("mult")) { ret = num1 * num2; } else if (op.equals("div")) { if (num2 != 0) ret = num1 / num2; else throw new DevideByZeroException("divide by zero"); } else { throw new RemoteException("invalid opertion : " + op); } return ret; } }
The calc() methods throws a java.rmi.RemoteException when an
unknown operator is used or the divisor is zero. In the web service client
program, you can use the java.rmi.RemoteException or java.lang.Exception
to catch this exception.
Web service clients can send an attachment via a SOAP message.
Although JAX-RPC specification defines a Java type mapping that corresponds to the MIME type attachment, web service clients can also map it to javax.activation.DataHandler type, regardless of the MIME type.
This section describes how to map a MIME type to a DataHandler type by using the WSDL-to-Java mapping tool.
The following is an example of a web service WSDL that contains a MIME part.
<message name="submission"> <part name="title" type="xsd:string" /> <part name="price" type="xsd:float" /> <part name="attachment" type="xsd:hexBinary" /> </message> . . . <operation name="submit"> . . . <input> . . . <mime:part> <mime:content part="attachment" type="application/xml" /> </mime:part> . . . </input> . . . </operation>
By default, when a service endpoint interface is generated by using wsdl2java based on the previous WSDL, the MIME type 'application/xml' becomes mapped to the javax.xml.transform.Source type.
public interface SubmitBook extends java.rmi.Remote {
public String submit(String title, float price,
javax.xml.transform.Source attachment)
throws java.rmi.RemoteException;
}
When the attribute called 'dataHandlerOnly' is set to 'true' through Ant task and wsdl2java, or when the '–datahandleronly' option is used through the command-line tool, the MIME part is always mapped to javax.activation. DataHandler type, regardless of the MIME type.
public interface SubmitBook extends java.rmi.Remote {
public String submit(String title, float price,
javax.activation.DataHandler attachment)
throws java.rmi.RemoteException;
}
Hence, the web service client must send the attachment as the DataHandler type.
The following is an example of the web service client that uses the DataHandler type.
// Creates a FileInputStream from the specified path name FileInputStream inputStream = new FileInputStream(new File("attachment/book.xml")); DataHandler dataHandler = new DataHandler(inputStream, "application/xml"); // Get a Service port SubmitBook port = new SubmitBookService_Impl().getSubmitBookPort(); String result = port.submit("Sample for a option: datahandleronly", 12.34f, dataHandler); System.out.println("response = " + result);
JAX-RPC specification defines Java type mappings for XML types. However, sometimes it is more convenient to send a message by defining a SOAPElement .
This section describes how to use the javax.xml.soap.SOAPElement type regardless of the XML type by using the WSDL-to-Java mapping tool.
The following is an example of a WSDL described as a Document/Literal.
<definitions name="BookQuoteService" ... > <types> <xsd:schema targetNamespace="..."> <xsd:complexType name="Book"> <xsd:sequence> <xsd:element name="title" type="xsd:string" /> <xsd:element name="isbn" type="xsd:string" /> <xsd:element name="authors" type="xsd:string" /> </xsd:sequence> </xsd:complexType> <xsd:element name="Book" type="mh:Book" /> <xsd:element name="Result" type="xsd:float" /> </xsd:schema> </types> <message name="getBookPriceRequest"> <part name="book" element="mh:Book" /> </message> <message name="getBookPriceResponse"> <part name="result" element="mh:Result" /> </message> . . . <binding name="BookServiceSoapBinding" type="mh:BookQuote"> <soap:binding style="document" ... /> <operation name="getBookPrice"> <input> <soap:body use="literal" ... /> </input> <output> <soap:body use="literal" ... /> </output> </operation> </binding> . . . </definitions>
The service endpoint interface generated by wsdl2java from the previous WSDL has the following input parameter.
public interface BookQuote extends java.rmi.Remote {
public float getBookPrice(sample.nodatabinding.stub.Book book)
throws java.rmi.RemoteException;
}
When creating an SEI through wsdl2java, if the attribute called 'noDataBinding' is set to 'true' through wsdl2java Ant task or the '–nodatabinding' option is set to 'true' through the command-line tool, the input parameters and return value types must always be javax.xml.soap.SOAPElement, regardless of the XML type.
public interface BookQuote extends java.rmi.Remote {
public javax.xml.soap.SOAPElement
getBookPrice(javax.xml.soap.SOAPElement book)
throws java.rmi.RemoteException;
}
The 'nodatabinding' option for wsdl2java is only valid in a Document/Literal WSDL.
In this case, the web service client developer must manually create a message as the SOAPElement type. The following is an example of the web service client that uses the SOAPElement type.
// Creates a FileDataSource from the specified path name SOAPFactory factroy = SOAPFactory.newInstance(); // Create a SOAPElement object SOAPElement book = factroy.createElement( "Book", "mh", "http://www.tmaxsoft.com/j2eews/BookQuote"); SOAPElement title = factroy.createElement("title"); title.addTextNode("Sample for a option: nodatabinding"); book.addChildElement(title); SOAPElement isbn = factroy.createElement("isbn"); isbn.addTextNode("123-456-789"); book.addChildElement(isbn); SOAPElement authors = factroy.createElement("authors"); authors.addTextNode("TmaxSoft Co., Ltd."); book.addChildElement(authors); // Get a Service port BookQuote port = new BookQuoteService_Impl().getBookQuotePort(); SOAPElement price = port.getBookPrice(book); System.out.println("price = " + price.getValue());