Datatypes
WebLogic supports a number of different Java types that can be used as parameter and return values for your web service operations. If you rely on these types, WebLogic automatically converts between their XML and Java representations. If a web service relies on complex types for instance, an operation accepts an input parameter representing an instance of a custom Java class you need a set of serialization classes that can convert between their XML and Java representations. There are two ways to generate this set of serialization classes:
Auto-generation of serialization classes
Given a class definition that adheres to certain reasonable constraints, WebLogic's Ant tasks let you automatically generate the required serialization classes and XML Schemas that represent the class.
Manual creation of serialization classes
If the class definition doesn't comply with these constraints, you must manually implement the serialization logic and specify the XML Schemas that represent the datatypes.
In this section, we look at how to work with the built-in types, to automatically create the required serialization classes and XML Schemas for any custom types, and to manually provide this support if needed.
19.4.1 Built-in Types
The built-in datatypes are those supported by the XML Schema specification (see the XML Schema datatype specification at http://www.w3.org/2001/XMLSchema). This mandates that all the built-in datatypes have the namespace name http://www.w3.org/2001/XMLSchema. The Java SOAP type will have a namespace name of http://schemas.xmlsoap.org/soap/encoding/. Figure 19-4 depicts the hierarchy of datatypes supported by the XML Schema standard. Let's look at how these built-in XML Schema datatypes map to the equivalent Java types:
Figure 19-4. A hierarchy of XML schema datatypes
- The primitive Java types boolean, byte, short, int, long, float, and double (and their wrapper Java classes) map to the XML Schema datatypes with the same name.
- anyURI, NOTATION, and all the subtypes of string map to the Java type java.lang.String. All array types NMTOKENS, IDREFS, and ENTITIES map to the Java String[] type. The primitive Java type char (and its wrapper class) maps to the XML datatype string with a facet of length=1.
- All date/time XML Schema types map to java.util.Calendar. In the case of the types gYearMonth, gYear, gMonthDay, gDay, and gMonth, only the relevant portion of java.util.Calendar is used. For example, the gYear type maps to the "year" attribute of the equivalent Calendar object. Conversely, the Java types java.util.Calendar and java.util.Date both map to the XML Schema type datetime.
- hexBinary and base64Binary map to the Java byte[] type.
- The XML Schema types integer and decimal map to the Java types java.map.BigInteger and java.math.BigDecimal, respectively. The integer subtypes also map to java.math.BigInteger.
- The unsigned XML Schema types map to their higher Java equivalents. For instance, unsignedLong maps to java.util.BigInteger, unsignedInt maps to the Java type long, and so on.
- Finally, QName maps to the Java class javax.xml.namespace.QName, and duration maps to weblogic.xml.schema.binding.util.Duration.
Refer to http://www.w3.org/TR/xmlschema-2/ for more information on XML Schema datatypes.
19.4.2 Custom Types
If a web service defines operations that uses custom datatypes or those that are not provided for by the XML Schema specification, you need the following support files for each such datatype:
- A Java class that represents the datatype.
- A serialization class that converts between the Java and XML representation of the data.
- Holder classes, if you intend to use the custom type as an out or in-out parameter.
- An XML Schema representation of the type, which is later included in the web-services.xml deployment descriptor.
- A datatype mapping, which maps the XML Schema to the actual serialization class. This mapping must be placed in the web-services.xml descriptor file.
You often will begin with a Java class that represents a custom datatype. For instance, you could wrap a web service around an existing backend component whose methods use custom Java classes as their parameters or return values. Conversely, if you need to interact with already published web services, the XML Schema representation of the datatype serves as the starting point for generating the other support files. In either case, WebLogic provides tools that can easily create the necessary files.
19.4.3 Generating Support Files for a Custom Java Class
If a web service operation defines parameters or return values that use custom datatypes, WebLogic can generate the required support files automatically from a given Java class if the following conditions are met:
- Your class must define a default, no-argument constructor.
- Your class must define a getXXX( ) method and a setXXX( ) method for each member variable that needs to be serialized.
- The type of any member variable that needs to be serialized must be either a built-in datatype, a collection of a supported datatype as described in Table 19-1, or a datatype for which you already have generated the serialization classes and XML Schema definitions.
If a custom type adheres to these requirements, WebLogic's servicegen and autotype Ant tasks let you generate the required support classes and schema. Table 19-1 lists the custom XML Schema types that WebLogic supports by default.
Java type |
Equivalent XML schema type |
---|---|
An array of any supported datatype |
SOAP array |
A JavaBean whose properties are declared to be of a supported datatype |
|
java.util.List |
SOAP array |
java.util.ArrayList |
SOAP array |
java.util.LinkedList |
SOAP array |
java.util.Vector |
SOAP array |
java.util.Stack |
SOAP array |
java.util.Collection |
SOAP array |
java.util.Set |
SOAP array |
java.util.HashSet |
SOAP array |
java.util.SortedSet |
SOAP array |
java.util.TreeSet |
SOAP array |
java.lang.Object |
|
JAX-RPC-style enumeration class |
with enumeration facets |
Thus, WebLogic supports an array or collection of any supported datatype. It also supports java.lang.Object, so long as the type of the runtime object matches one of the built-in datatypes, or a custom type for which the required support files have been generated. The Java class in Example 19-8 satisfies these constraints:
Example 19-8. A suitable candidate for automatic generation
public class MyType implements java.io.Serializable { String name; ArrayList myPets; // ArrayList of type String public void setName(String name) { this.name = name; } public String getName( ) { return name; } public void setPets(ArrayList myPets) { this.myPets = myPets; } public ArrayList getPets( ) { return myPets; } }
In order to generate the necessary support files, you should use the autotype Ant task. The following portion from an Ant script shows how to invoke the task:
This Ant task requires you to specify the name of the Java class that represents the custom type, the target namespace to be associated with the custom type, the package name to be used by the generated Java classes, and the destination directory in which the output files should be placed. There are other attributes you can specify when invoking the autotype task, which we cover later in this chapter. The example Ant task generates the following files in the output directory:
types.xml
This file contains the XML Schema definition for the Java class and the type mappings. Other Ant tasks, such as servicegen, can take this file as a parameter and use it to include support for the custom datatypes.
mypackage/name/MyTypeCodec.java
This file includes the Java source for the serializer/deserializer that can be used to convert between XML and Java representations of the custom type.
com.oreilly.wlguide.webservices.simple.holders.MyTypeHolder.java
This file includes the Java source for the type holder classes, and is needed if the custom datatype is used to define out and in-out parameters of a web service operation.
In order to use these generated files, you must execute the following tasks:
- Compile the Java source files and package them along with other compiled classes used by your web services.
- Splice the generated types.xml file into the web-services.xml file. This means that the types element in the web-services.xml descriptor should be changed to include the XML Schema generated in the types.xml file.
- Modify the type-mapping element in the web-services.xml descriptor to include the type mappings generated in the types.xml file.
19.4.3.1 Using the servicegen Ant task
The servicegen task can also perform all of these responsibilities when generating a deployable EAR from a backend component. You can instruct the servicegen task to generate the necessary support files using the generateTypes="True" attribute. Suppose you extend the backend Java class illustrated earlier in Example 19-1 to include a method that accepts a parameter of type MyType. For example, suppose you modify the Simple class to include the following Java method:
public String foo(MyType mytype) { return mytype.getName( ); }
Then you need to change the servicegen task to generate the necessary support files for the custom type. In fact, the servicegen task will perform the same tasks as the autotype task, and also insert the necessary XML to the web-services.xml descriptor file and include the generated Java classes in the output EAR file. So, you need to invoke the following Ant target to generate a deployable EAR:
generateTypes="True" expandMethods="True">
The servicegen task used with this attribute automatically detects any nonbuilt-in datatypes that are used as parameters or return values and generates the appropriate XML Schema definitions and Java classes that handle the serialization of these datatypes.
You also can test operations that use custom types from the automatically generated home page. However, for custom-type parameters, WebLogic provides an input area where you can specify your own XML for creating an instance of the data type. In fact, WebLogic provides a sensible template for modeling an instance of the custom type. In the case of the earlier MyType, the template will resemble this XML fragment:
sample string
19.4.4 Autotyping
The autotype task is quite versatile because it can generate the required support classes from three sources:
- It can examine a Java class that represents the custom datatype and generate the required serialization classes, holder classes, XML Schema, and type mappings, provided the class adheres to certain conditions. We already saw an example of this in which the javatypes attribute of the Ant task lets you specify the Java class that represents the custom type.
- It can generate the necessary support classes from an XML Schema that defines the custom type. Use the schemaFile attribute to supply the name of the schema file in this case.
- It also can examine a WSDL file and generate the support classes from the relevant portions of the WSDL. In this case, you must use the wsdl attribute to supply the pathname or the URI of the WSDL file:
wsdl="mywsdl.xml" targetNamespace="http://oreilly.com/wlguide/auto" packageName="mypackage.name" destDir="outtypew"/>
The preceding Ant target generates the same output: the types.xml file, the Java class that represents any custom datatypes, and the serialization classes. Let's now examine how the autotype task can generate the required support classes from an XML Schema.
19.4.4.1 Using an XML Schema
Consider the XML Schema representation for a custom type, as shown in Example 19-9.
Example 19-9. Schema representation of a datatype
The schema defines two properties for a custom type: a string-valued property name, and an age property that accepts any integer value in the range 1-144. Thus, WebLogic can also handle restrictions imposed on property types, as we have done in this example. If the mytypeschema.xsd file holds this XML Schema definition, you can use the autotype task to generate the required support classes using this file:
schemaFile="mytypeschema.xsd" keepGenerated="True" targetNamespace="http://oreilly.com/wlguide/auto" packageName="com.oreilly.wlguide.webservices.customtype" destDir="outtypes"/>
Here the schemaFile attribute refers to a file that holds a valid schema definition for a custom type. In this case, the Ant task will generate the appropriate serializer and deserializer classes, as well as the JavaBean class that represents this type and the types.xml mapping file. The JavaBean class for the XML Schema defined in Example 19-9 will look something like this:
public class SimpleSchemaBean implements java.io.Serializable { public SimpleSchemaBean( ) {} public SimpleSchemaBean(java.lang.String p_name, java.math.BigInteger p_age) { this.name = p_name; this.age = p_age; } // ... }
Note how the age property of the JavaBean uses the java.math.BigInteger type. This is in accordance with the mapping rules for XML Schema types described earlier. The generated serializer and deserializer classes will also include checks to ensure that the XML Schema restrictions are adhered to.
This mechanism for generating the required support classes is quite robust. Let's examine the rules that determine the eventual structure of the JavaBean for the XML Schema:
- A JavaBean is created for each element found in the XML Schema. The element may include both simple and complex types, or just simple content. Abstract datatypes are mapped to an abstract Java class.
- Any element within an element is mapped to a property of the JavaBean. Any element that relies on a that is derived by restriction from an existing simple type is mapped to a property whose type corresponds to the equivalent Java datatype. However, the serialization and deserialization routines are augmented to handle any restrictions placed on the existing simple types.
- If one or more facets are associated with an element, you may use only the following base primitive types: string, decimal, float, and double. However, the pattern facets are not enforced by the associated serializer and deserializer classes.
- A complex type derived from a simple type is mapped to a JavaBean property called simpleContent of type String. A complex type derived from another by extension is mapped through Java inheritance.
- The element is mapped to the java.lang.Object type.
Table 19-2 lists how some of the other XML Schema types are mapped to their equivalent Java types.
XML schema datatype |
Equivalent Java datatype |
---|---|
java.lang.Object |
|
or |
A Java null value. If the XML datatype is built-in or maps to a primitive Java type, the XML datatype maps to the equivalent Java object wrapper type (e.g., java.lang.Integer). |
An array of the list datatype. |
|
Enumeration |
A typesafe enumeration pattern as described by the JAX-RPC standard. |
Array derived from soapenc:Array by restriction using the wsdl:arrayType attribute |
An array of the Java equivalent of the arrayType attribute. |
Array derived from soapenc:Array by restriction |
An array of the Java equivalent. |
19.4.4.2 Round-tripping
Suppose you use the servicegen task to generate a web service that relies on the Java class MyType.java illustrated earlier in Example 19-8. Suppose that later you use the autotype task to manufacture the required support classes from the WSDL file generated for the web service. You'll find that the MyType class generated by the autotype task doesn't exactly match your original definition for the MyType class. The original definition for the custom type included a property myPets of type java.util.ArrayList:
ArrayList myPets; //... public void setPets(ArrayList myPets) { this.myPets = myPets; }
The generated Java class that represents the custom datatype includes a slightly different declaration:
private java.lang.Object[] pets ; //... public void setPets(java.lang.Object[] v) {this.pets = v; }
Even though the custom datatype isn't represented in the same way, both versions of the Java class that represents the custom type are functionally equivalent. Thus, you cannot always round-trip the process of generating the XML Schema from the Java class and then re-create the same Java class from the generated XML Schema. Similarly, if you use the autotype task to generate the Java equivalent of an XML Schema definition, and again use autotype to create an XML Schema from the generated Java class, the original and generated versions of the XML Schema that represents the custom type may not be identical. Thus, even though the autotype task works in both directions, round-tripping doesn't guarantee the same results, only a version that is functionally equivalent to the original.
This side effect of round-tripping has important implications. Suppose that you use the servicegen task to create a deployable EAR for a web service that wraps a standard Java class. The task also creates the serializer classes and the Java/XML representations of any custom datatypes used by the web service. Later, you may use the clientgen task to generate a client JAR for the web service, and the client JAR also includes the serializer class and Java/XML definitions for any custom types. Because the clientgen task (by default) looks at the generated WSDL to create these support files, you are essentially trying to round-trip. Thus, the client-side Java/XML representations may not be compatible with the server-side Java/XML definitions of the custom type, which will create potential problems.
Rather than maintain separate versions of the Java and XML representations of the custom type, it would be desirable if the client JAR could reuse the server-side support classes generated for the custom type. Fortunately, by using the useServerTypes="true" attribute, you can instruct clientgen to rely on the server-side generated serializer class and the Java/XML representations of the custom type included within the EAR file that packages the web service. Of course, you must then ignore the wsdl attribute and simply use the ear attribute to refer to the existing EAR file. Alternatively, you also could use the client subelement of the servicegen task to generate the client JAR during the assembly of the web service. In this case, the client JAR automatically will use any server-side support classes generated for the custom datatypes.
19.4.5 Manually Creating the Support Classes for a Custom Type
Recall that in order for WebLogic to support web services that use custom types, you need to supply the following information:
- An XML Schema that defines the structure of the custom datatype
- A Java class that describes a Java representation of the datatype
- A serialization class that converts between the XML and Java representations of the data
- Mapping information for the custom type in the web-services.xml descriptor file
As we've seen already, WebLogic's support for autotyping can generate these items for you by introspecting the backend components for your web service. In fact, the servicegen and autotype Ant tasks can handle many custom types. Still, there may be situations in which the Ant task is unable to correctly support your custom type, or perhaps you need to control how the data is converted between its XML and Java representations. In such instances, you must manually create the support files required for the custom datatype.
19.4.5.1 Creating the XML Schema and Java class for a datatype
Consider the XML Schema for a nonbuilt-in type, defined earlier in Example 19-9. The XML Schema defines the structure of a custom type, SimpleSchemaBean, in terms of two properties: name, which is a string-valued property, and age, whose value is any positive integer no greater than 144. Thus, the following XML is a valid instance of the custom type defined by the XML Schema:
Mountjoy 43
Example 19-10 depicts the source for a JavaBean that can hold the data captured by the XML Schema defined earlier.
Example 19-10. A JavaBean representation of the XML Schema
package com.oreilly.wlguide.webservices.fromJavaType; public class MySimpleSchemaBean implements java.io.Serializable { private String surname; private int age; public MySimpleSchemaBean( ) {}; public String getSurname( ) { return surname; } public void setSurname(String surname) { this. surname = surname; } public int getAge( ) { return age; } public void setAge(int age) { this.age = age; } }
A quick glance at the source code reveals two points:
- The name of the JavaBean isn't the same as the value of the name attribute of the element in our XML Schema.
- The XML Schema defines a complex type, SimpleSchemaBean, which exposes a name property, whereas the corresponding JavaBean exposes a surname property. The two property names are not identical.
These differences are superficial and were included intentionally to illustrate the flexibility you have when designing the Java class and XML Schema for the custom type. The web-services.xml descriptor file will map the XML Schema to the corresponding Java class, thus you are free to specify different names for the custom type. Moreover, your custom serialization logic will ensure that the XML Schema property name is mapped to the JavaBean property surname.
19.4.5.2 Creating the serialization class
Once you've defined the XML Schema and the Java class for the custom type, you must define the serialization class that converts between instances of the XML Schema to instances of the Java class, and vice versa. This conversion relies on WebLogic's XML Streaming API, which provides an event-driven mechanism for handling incoming XML documents and an intuitive way for building new XML documents (the XML Streaming API is covered in more detail in Chapter 18). Typically, the serialization class will extend the weblogic.webservice.encoding.AbstractCodec class, which also implements several interfaces: weblogic.xml.schema.binding.Serializer, javax.xml.rpc.encoding.SerializerFactory, and others.
Our serialization class will effectively build on the following skeleton code:
public class MyCodec extends weblogic.webservice.encoding.AbstractCodec { public void serialize(Object obj, XMLName name, XMLOutputStream writer, SerializationContext ctx) throws SerializationException { // convert the incoming obj to an XML stream } public Object deserialize(XMLName name, XMLInputStream reader, DeserializationContext ctx) throws DeserializationException { // convert the XML stream to a Java object } public Object deserialize(XMLName name, Attribute att, DeserializationContext ctx) throws DeserializationException { // convert the XML attribute to a Java object } }
Use the serialize method to convert data from Java to XML. Here, the Object parameter holds an instance of the Java type MySimpleSchemaBean. The XMLOutputStream parameter allows you to use the Streaming API to generate a valid instance of the XML Schema for the datatype. The XMLName parameter specifies the name of the resulting element that is generated. Here is a straightforward implementation of the serialize( ) method:
public void serialize(Object obj, XMLName name, XMLOutputStream writer, SerializationContext ctx) throws SerializationException { MySimpleSchemaBean m = (MySimpleSchemaBean) obj; //add start element writer.add(ElementFactory.createStartElement(name)); //add name element writer.add(ElementFactory.createStartElement("name")); writer.add(ElementFactory.createCharacterData(m.getSurname( ))); writer.add(ElementFactory.createEndElement("name")); //add age element writer.add(ElementFactory.createStartElement("age")); writer.add(ElementFactory.createCharacterData(Integer.toString(m.getAge( )))); writer.add(ElementFactory.createEndElement("age")); //add outer start element writer.add(ElementFactory.createEndElement(name)); }
Use the deserialize( ) method to convert the data from XML to its Java equivalent. Our implementation maps the incoming XML contained in the XMLInputStream parameter to an instance of the Java type MySimpleSchemaBean. Again, the XMLName parameter specifies the expected name of the XML element MySimpleSchemaBean. Here is a simple implementation that skips over XML elements until it finds the name and age elements. It then extracts the character data and uses this to construct an instance of the Java type MySimpleSchemaBean:
public Object deserialize(XMLName name, XMLInputStream reader, DeserializationContext ctx) throws DeserializationException { MySimpleSchemaBean m = new MySimpleSchemaBean( ); try { if (reader.skip(ElementFactory.createXMLName("name"))) { reader.next( ); // skip over name start element CharacterData cdata = (CharacterData) reader.next( ); m.setSurname(cdata.getContent( )); } else { throw new DeserializationException("name not found"); } if (reader.skip(ElementFactory.createXMLName("age"))) { reader.next( ); // skip over age start element CharacterData cdata = (CharacterData) reader.next( ); m.setAge(Integer.parseInt(cdata.getContent( ))); if (m.getAge( ) > 144) throw new DeserializationException("age value not valid"); } else { throw new DeserializationException("age not found"); } // Always read the entire XML representing your data if (reader.skip(name, XMLEvent.END_ELEMENT)) reader.next( ); // skip over the last element else throw new DeserializationException("end element not found"); } catch (XMLStreamException e_xs) { throw new DeserializationException("problem deserializing"); } return m; }
The deserialize( ) method also includes extra logic to enforce the restrictions on the age property, defined earlier in the XML Schema for the custom type. Typically, you will use the deserialize( ) method to perform additional checks and ensure that the Java instance is a valid instance of the Java type. The method implementation also reads until the end of the last element in the incoming XML. This is important because otherwise, the deserialization of subsequent XML elements may fail.
The second version of the deserialize( ) method is required only if you need to map an XML attribute to a Java object. In this case, we don't need to implement this method.
19.4.5.3 Including type information in the web-services.xml descriptor
At this stage, you've created the Java and XML Schema representations for the custom type, and the serialization class that converts between the two forms. Now you need to specify this type information for each custom type used by a web service. As always, this information must be embedded within the web-services.xml descriptor file. If you are going to create your web service with the servicegen task, place the type mappings in a separate file (say, types.xml) and refer to this file from the task. If you've already created a web-services.xml descriptor for the web application, for each nonbuilt-in datatype you need to make the following changes in the web-services.xml descriptor:
- Include the XML Schema for the custom type within the types element:
- Use the type-mapping element to map the XML datatypes to the equivalent Java types. Each type-mapping-entry element includes a namespace declaration for the custom type, the fully qualified name of the Java class, the name of the XML Schema type, and the name of the serializer and deserializer classes. For the XML datatype MySimpleSchemaBean, you must specify the following type-mapping information:
Once you make these changes to the web-services.xml descriptor, you can define web service operations that use these custom types as parameters and/or return values.
19.4.5.4 Using the servicegen task
WebLogic's servicegen task lets you assemble a web service for which you have manually created the required support files for each nonbuilt-in type. This includes the Java class and XML Schema that describe the structure of the custom type, as well as a file that holds the type-mapping information. In such cases, you need to disable the autotyping feature when invoking the servicegen task. The following portion from an Ant script shows how to use the servicegen task to assemble a web service while relying on the support files that were created manually for any custom types:
javaClassComponents="com.oreilly.wlguide.webservices.fromJavaType.Simple" serviceName="Simple" serviceURI="/Simple" targetNamespace="${namespace}" generateTypes="False" typeMappingFile="types.xml" protocol="http" expandMethods="True">
Notice how the generateTypes="False" attribute lets you disable the autotyping feature. The typeMappingFile attribute specifies the location of the file that includes the mapping information for all custom types used by the web service. Now imagine that a web service operation is implemented through the following method of the backend component:
public String getName(MySimpleSchemaBean mytype) { return mytype.getSurname( ); }
The getName( ) method accepts an input parameter of type MySimpleSchemaBean. When you invoke the servicegen task to package the web service into a deployable EAR, it uses the supplemented mapping information in the types.xml file to generate any references to the support classes. So, if you examine the web-services.xml descriptor file generated by the servicegen task, you find the following definition for the web service operation:
Moreover, you'll notice that the type-mapping element in the web-services.xml descriptor file contains the mapping information held in the types.xml file. Unfortunately, the generated web-services.xml descriptor does not include the XML Schema definitions for any of the custom datatypes. This means that you must manually edit the web-services.xml descriptor file and explicitly include the XML Schemas for any custom types. As explained earlier, these XML Schemas must be placed verbatim within the types element, which occurs before the type-mapping element. To accomplish this change, you must unpack the myEar.ear file, edit the web-services.xml file to include the XML Schemas, and then repackage the EAR file.
19.4.6 Implementing Multiple Return Values
Usually, a web service operation returns a single value, which is the return value of the EJB method or a method on a Java class used to implement the operation. If the operation must return multiple values, you can redesign the backend method so that it returns an instance of a complex type say, an object with multiple attributes or an array itself. Alternatively, the operation could define one or more parameters to be out or "in-out" parameters. The JAX-RPC standard lets you define web service operations with parameters that are either in, out, or in-out. An in parameter specifies the type of data input into the web service operation. An out parameter specifies the type of data output from the web service operation. Finally, an in-out parameter is used by the client to send data to and receive data from the operation.
It is the out and in-out parameters that let you explicitly define multiple return values from the web service operation. An out parameter has an undefined value when the operation is invoked, but acquires a value before the backend method completes. In contrast, an in-out parameter behaves like an in parameter and an out parameter at the same time. It is assigned a value before the operation is invoked, and a new value before the backend method completes servicing the web service operation. An out parameter can be treated just like a return value. By defining multiple out parameters, a web service operation is able to return multiple values to the client. An in-out parameter allows the web service operation to return a value of the same type as the incoming value.
Suppose we wish to add a holderExample( ) method to Example 19-1, which accepts an in-out parameter and an out parameter of type String. The following fragment from the web-services.xml descriptor file depicts the signature for this operation:
In such a case, you need to specify the corresponding holder class as the types of the parameters. The use of out or in-out parameters always requires you to change the backend code to use holder classes. The holderExample( ) method must therefore be implemented as follows:
public void holderExample(StringHolder inout, StringHolder out) { inout.value = inout.value.toUpperCase( ); out.value = "someOutValue"; }
Effectively, the method returns two values, inout and out. It also could have returned a standard value using a return type. All out and in-out parameters must implement the javax.xml.rpc.holders.Holder interface. The backend method can use the Holder.value attribute to obtain the input value passed to the in-out parameter and use it to assign the output value for the out and in-out parameters. The following code fragment shows how a client would invoke the modified holderExample operation:
javax.xml.rpc.holders.StringHolder oString = new javax.xml.rpc.holders.StringHolder( ); javax.xml.rpc.holders.StringHolder ioString = new javax.xml.rpc.holders.StringHolder( ); ioString.value = "Incoming"; Simple ws = new Simple_Impl("http://10.0.10.10:7001/pojoHolderService/Simple?WSDL"); SimplePort port = ws.getSimplePort( ); port.holderExample(ioString, oString); System.err.println("in/out string is now: " + ioString.value); System.err.println("out string is now: " + oString.value);
Notice how the client uses the Holder.value attribute to obtain the results of the web service operation. The output should look like this:
in/out string is now: INCOMING out string is now: someOutValue
WebLogic provides support for in-out parameters if you use the servicegen Ant task. When you use this task to generate a service from a backend that you provide and your backend uses the Holder classes in its implementation, WebLogic will automatically ensure that the web-services.xml file describes the relevant parameters as in-out. If you want the parameter to be an out parameter instead, you will have to edit the generated descriptor file manually.
19.4.6.1 Creating holder classes
The JAX-RPC standard defines a number of holder classes for the built-in types. Table 19-3 lists the holder classes WebLogic provides for the various standard types.
Java datatype |
Built-in holder class |
---|---|
boolean |
javax.xml.rpc.holders.BooleanHolder |
byte |
javax.xml.rpc.holders.ByteHolder |
short |
javax.xml.rpc.holders.ShortHolder |
int |
javax.xml.rpc.holders.IntHolder |
long |
javax.xml.rpc.holders.LongHolder |
float |
javax.xml.rpc.holders.FloatHolder |
double |
javax.xml.rpc.holders.DoubleHolder |
java.math.BigDecimal |
javax.xml.rpc.holders.BigDecimalHolder |
java.math.BigInteger |
javax.xml.rpc.holders.BigIntegerHolder |
byte[] |
javax.xml.rpc.holders.ByteArrayHolder |
java.util.Calendar |
javax.xml.rpc.holders.CalendarHolder |
javax.xml.namespace.QName |
javax.xml.rpc.holders.QnameHolder |
java.lang.String |
javax.xml.rpc.holders.StringHolder |
In general, the autotype Ant task can generate the holder classes for any custom types that a web service uses as parameter or return values. You may, of course, implement the holder classes for your custom datatypes manually. Example 19-11 illustrates the holder class for the custom Java type SimpleSchemaBean.
Example 19-11. Holder class for the SimpleSchemaBean type
package com.oreilly.wlguide.webservices.simple.holders; import com.oreilly.wlguide.webservices.simple.SimpleSchemaBean; public final class SimpleSchemaBeanHolder implements weblogic.xml.schema.binding.Holder { public SimpleSchemaBean value; public SimpleSchemaBeanHolder( ) {} public SimpleSchemaBeanHolder(SimpleSchemaBean value) { this.value = value; } }
Besides implementing the javax.xml.rpc.holders.Holder interface, this class also adheres to the following guidelines:
- The holder class is named TypeHolder, where Type represents the name of the custom Java type.
- The holder class is placed in a "holders" subpackage, just below the package for the custom Java type. For instance, if the custom type Foo is located in the package a.b.c, the FooHolder implementation class must be located within the a.b.c.holders package.
- The class must declare a public field called "value" that represents an instance of the custom Java type. In addition, the class defines a default constructor that initializes the value of the field to some default, and another constructor that sets the value of the field to the value of the incoming parameter.
19.4.7 SOAP Attachments
Some datatypes are transported as SOAP attachments, with the appropriate MIME type, rather than as elements within the body of the SOAP message. For example, a java.awt.Image object is transported as an attachment with MIME type image/gif or image/jpeg. Similarly, a javax.mail.internet.MimeMultipart object is transported as a message attachment with MIME type multipart/*, and a javax.xml.transform.Source object is transmitted as an attachment with its MIME type set to either text/xml or application/xml. If your code uses any of these datatypes for a parameter or return value, servicegen automatically will ensure that the instance data is transferred as a SOAP attachment. As a result, the location attribute of a param element within the generated web-services.xml descriptor file will be set to attachment.
By default, servicegen treats java.lang.String as a built-in datatype. Thus, any string parameter or return value will be sent in the SOAP body as an XML Schema string type. Instead, if you wish to send a string value as a SOAP attachment with the text/plain MIME type, you must edit the web-services.xml descriptor file manually and then change the location attributes accordingly.
Note that WebLogic's SOAP message encryption cannot be configured to encrypt SOAP attachments. It can encrypt only selected parts of a SOAP message body.