Using the J2EE Connector Architecture and SOAP to Build Web-Service-Ready Enterprise Applications, Part 2
In December 2002, we published part 1 of this series and received many valuable comments from you. In writing part 2 of the series, we've incorporated your suggestions in the following descriptions:
- How to use Sun ONE Connector Builder to implement tightly and loosely coupled interactions
- How to build connectors that support Simple Object Access Protocol (SOAP) with attachments and SOAP over Hypertext Transmission Protocol, Secure (HTTPS).
Note - The source code in this article is extracted from the Customer Order Tracking System (COTS) sample resource adapter that's shipped with Sun ONE Connector Builder. To adopt the sample code, download Sun ONE Connector Builder and deploy the COTS resource adapter. Alternatively, treat the sample as a general guideline only and use its approach as a reference when developing your own resource adapter.
The "What" and "Why" of Sun ONE Connector Builder 2.0
Sun ONE Connector Builder 2.0 comprises a set of tools, components, and libraries with which you can build resource adapters that comply with Java 2 Platform, Enterprise Edition (J2EE) Connector Architecture 1.0 for Enterprise Information Systems (EIS) and legacy applications. You can access the resource adapters with the Common Client Interface (CCI) API as described in the J2EE Connector 1.0 Specification. Optionally, you can add a SOAP service layer to the resource adapters and build Web service solutions for integrating enterprise applications.
The resource adapters take advantage of the J2EE Connector Architecture Service Provider Interface (SPI) implementation, which provides connection management, transaction control, and authentication. Thus, with Sun ONE Connector Builder, you can capitalize on your investments in existing custom or unique applications and integrate them with other J2EE Web service applications.
Sun ONE Connector Builder-Generated, Web-Service-Ready Connectors
Sun ONE Connector Builder 2.0 generates the following to enable connectors with SOAP services:
- SOAP service endpoints: Java Interaction Objects (JIOs)
- Deployment descriptors for the service endpoint
- Java object-to-XML (or vice versa) serializers and deserializers
- SOAP service clients
- Ant build scripts to package SOAP services, deployment descriptors, and clients
In addition, Sun ONE Connector Builder 2.0 contains a tool for deploying SOAP services.
Note - Sun ONE Connector Builder 2.0 generates SOAP services, which you can deploy into Apache SOAP 2.2. To prepare SOAP services that are based on Java API for XML-based remote procedure calls (JAX-RPC), use the JAX-RPC support in Sun ONE Studio 4.
SOAP Service Endpoints: Java Interaction Objects (JIOs)
In this scenario, you deploy a resource adapter in a Web server and interact with the resource adapter from a remote client. For this to happen, the resource adapter must be either servlet-wrapped or Remote Method Invocation (RMI)-enabled. To learn the details, see part 1 of this series.
Note - Because of the extensive nature of the CCI and potential serialization issues, RMI enablement of the CCI may be too costly. The problem worsens if you create the resource adapter on top of native client libraries. Here's a suggestion: Wrap the resource adapter's interaction specification with servlets, which accept or return XML as input or output.
SOAP offers an intuitive standard encoding. Since the CCI is at too low a level to be exposed through SOAP, it's wrapped with JIOs. A JIO is a Java object that's generated according to the resource adapter's interaction specification. A JIO accepts the required variables to drive the CCI as input and returns the results as output. In this case, you generate the JIOs as part of the resource adapters with Sun ONE Connector Builder.
The JIO contains two sets of overloaded methods:
execute-- The variants of this method act on the CCI interaction specification and input and output records. Therefore, those variants accept the input record and return the output record. The variants take the following as input parameters:- Input record only
- Input record and
ConnectionFactoryJava Naming and Directory Interface (JNDI) lookup name - Input record, JNDI lookup name, and
JNDIEnvcontext
All three variants return the output record.
executeAPI-- Three methods act directly on the EIS API's input-output parameters. Therefore, they accept all individual API input parameters and return the API's return parameter. The variants take the following as input parameters:- The API's input parameters only
- The API's input parameters and the
ConnectionFactoryJNDI lookup name - The API's input parameters, the
ConnectionFactoryJNDI lookup name, and theJNDIEnvcontext
All three variants return the API's return parameter.
See the sample code for a generated JIO for the GetCustomer interaction specification of the COTS sample resource adapter that's shipped with Sun ONE Connector Builder.
Deployment Descriptors for the Service Endpoint
You need a deployment descriptor to register a service in the SOAP runtime environment. The descriptor must comply with the format required by the runtime. The Apache SOAP runtime requirements are:
- Service Unique Uniform Resource Name (URN) -- Unique name for the service
- Provider information -- The provider type, scope, class name, method name, and such
- Type mapping -- The encoding style, the fully qualified class name, and the Java object-to-XML (and vice versa) serializer and deserializer class names for each data type that participates in the object graph of the input variable and the output variable
Each deployment descriptor lists its corresponding JIO class as the provider class and exposes the execute and executeAPI methods of the JIO as SOAP accessible. In addition:
- execute acts on the CCI input-output records and interaction specifications. Hence, if you use
execute, you must construct the input record in the SOAP request. executeAPIacts on the API method parameters. Hence, if you useexecuteAPI, you can send the API method parameters in the SOAP request. The deployment descriptor also lists the type mappings for each unique class that occurs in the input and output parameter graph, along with details of its Java object-to-XML (and vice versa) class names (deserializers and serializers).
See the sample code for a generated deployment descriptor for the COTSAPIGetCustomer JIO.
Java Object-to-XML (and Vice Versa) Serializers and Deserializers
Depending on the requirements you specify for the types of existing input-output parameters, Sun ONE Connector Builder also creates the required Java object-to-XML (or vice versa) template serializers or deserializers.
For each input-output parameter type, Sun ONE Connector Builder prompts you to specify the serializer or deserializer class information. For each data type, you can specify the following:
- Whether it's a JavaBeans component (bean)
- Whether Sun ONE Connector Builder must generate a template type-mapping class or the name of the data type's serializer or deserializer class
The template type-mapping class that is generated contains the marshall and unmarshall methods that you need for implementation. Because the Apache SOAP runtime provides the marshalling or unmarshalling classes after a generic installation, Sun ONE Connector Builder skips the objects of certain types. For collections, you must specify the participating classes and information on their corresponding serializers or deserializers.
The generated type-mapping class implements the serializer and deserializer interfaces as required by the Apache SOAP runtime. The class contains two methods-- marshall (Java object to XML) and unmarshall (XML to Java object), both user customization points.
See the sample code for a generated template serializer-deserializer class for a non-bean data type.
SOAP Service Clients
The resource adapter is enabled for interactions between SOAP and HTTP through Java Interaction Objects (JIOs) and other generated support infrastructure. Once those services are deployed in SOAP runtime, you can access them from external clients by posting a SOAP XML message to the SOAPRPCRouter servlet provided by Apache SOAP. Apache SOAP also provides certain helper classes and framework for clients to construct the SOAP XML message and interaction with the Apache SOAP runtime. Generation of the client stub consists of creating a Java class for each JIO-based SOAP service that's developed on top of the resource adapter.
Each generated client contains four methods. main is the starting point of execution, in which the class instantiates itself and calls the execute method. The Return parameter that execute returns is printed inside main. This process then follows:
executecalls theinitClientmethod, which creates a SOAP mapping registry and fills it with the details of the type mappings.- execute creates a SOAP "call" object and sets on it the values of the method name, encoding style, and mapping registry.
executesets the parameters on the call object by calling the methodpopulateInputData--auser customization point--which creates a vector of input parameters, adds the individual input parameters to the vector, and returns it.executesets the input parameter details on the call object, then invokes thecall.invoke()method with the URL of the SOAP service.executechecks the response for any SOAP fault.- In the absence of faults,
executereturns the response value tomain. - If a fault exists,
executeprints the details of the fault and returnsnull.
- In the absence of faults,
See the sample code for a generated SOAP Client for the COTSAPIGetCustomer SOAP service.
Ant Build Scripts to Package SOAP Services and Clients
Sun ONE Connector Builder generates an Ant-based build script ( build.xml) for each resource adapter--a script that contains the targets you then adopt to compile and package generated SOAP services. For example, build.xml contains the target package_soap, with which you can package the Java Interaction Objects (JIOs) along with the resource adapter.
To accommodate deployment and use, Sun ONE Connector Builder also packages the deployment descriptors and SOAP service clients into separate Java archive (JAR) files.
By using the Sun ONE Studio IDE or by directly running build.xml, you can execute those targets.
Deployment Tool for SOAP Services
Sun ONE Connector Builder provides a command-line tool, icwsdeploy, for deploying and undeploying resource adapter SOAP services into the target server environment. icwsdeploy accepts the deployment descriptors package created with the build script and deploys the SOAP services accordingly.
Other Features for Generated SOAP Services
This section describes two other procedures you can add on top of the SOAP services that are generated by Sun ONE Connector Builder:
HTTPS Access
You can securely access the SOAP services generated by Sun ONE Connector Builder by enabling them for SOAP-over-HTTPS interaction. Do the following:
- Secure the Web server by configuring it to use the Secure Sockets Layer (SSL).
This step is server specific; see your server administration documentation.
- Modify the generated SOAP service client code to enable SOAP-HTTPS interaction.
Follow these steps to enable SOAP service clients to use secure communication.
Note - The sample code below provides general guidelines. It runs in the Java 2 Platform, Standard Edition, (J2SE) environment only. In your server environment, follow similar--but perhaps not the exact--steps.
Use keytool to create a client.keystore file for SSL authentication. Be sure to specify this file in the client code. Type:
% jdk1.3.1/bin/keytool -import -alias
alias_name
-file client.cer -keystore client.keystore
When prompted, type a password.
If this is the first time you type the command, type any password.
The client.cer certificate in this directory is valid with servers that are secured with the Verisign 14-day trial SSL certificates. Thus, with client.cer, you can create your client.keystore file.
In case of errors, create client.keystore from a certificate that you install on your browser to access the Web server through HTTPS.
Download Java Secure Socket Extension (JSSE) and copy the three JAR files ( jsse.jar, jnet.jar, and jcert.jar ) into the generated resource adapter's lib/ext directory.
Modify the client code, as follows:
Add these two import statements to the client code:
import javax.net.ssl.SSLSocketFactory;
import java.security.Security;
Add the following three lines in the initClient() method:
// Specify where to find key material for the default
// TrustManager (specify the correct location of the file).
System.setProperty("javax.net.ssl.trustStore",
"C:\jdk1.3.1\bin\client.keystore"
);
// Use Sun's reference implementation of a URL handler
// for the "https" URL protocol type.
System.setProperty("java.protocol.handler.pkgs",
"com.sun.net.ssl.internal.www.protocol");
// Dynamically register Sun's SSL provider.
Security.addProvider(new
com.sun.net.ssl.internal.ssl.Provider());
Specify the correct value in the URL field, as follows:
theURL = new URL("https://localhost:483/soap/servlet/rpcrouter");
See the sample code for creating a customized SOAP service client to access the COTSAPIGetCustomer service with SOAP over HTTPS.
For details on setting up the clients for secure communication, see the paper, Setting up Apache Tomcat and a Simple Apache SOAP Client for SSL Communication, on the Apache site.
Attachment Handling
You can directly transmit most of the regular data types, such as String and Date, in the SOAP message body. However, if you must pass on a Binary Large Object (BLOB) as a method parameter, the SOAP body cannot hold the object's value. As a resolution, the World Wide Web Consortium (W3C) has specified in its SOAP with Attachments Specification a mechanism to pass on such values as attachments to the SOAP body. Apache SOAP 2.2 supports that specification.
The following sections explore two implementation approaches that use the attachment handling mechanism and point out the pros and cons of each approach. To understand the approaches, consider a Java Interaction Object (JIO) with the method void executeAPI( String name, Image img), which saves an image with a name. The implementation of the JIO interacts with the resource adapter to do its job.
Approach 1: Use a Custom Serializer
To send BLOBs in SOAP RPC, you can create a custom serializer for that object type instead of using the MimePart serializer. In this approach, the custom serializer can use the built-in Base64Serializer in Apache SOAP 2.x. Also, rather than being sent as a MIME attachment, the actual object data are part of the SOAP body as a base 64-encoded byte stream.
Here are the changes you must make to components:
- SOAP service -- The SOAP runtime at the server side receives the request. When it sees the type specified as
java.awt.Image, it calls the custom serializer to deserialize the data into an Image type. It then callsexecuteAPIwithStringandImageobject parameters. You need not customize JIOs generated by Sun ONE Connector Builder. - Custom serializer -- The custom serializer uses the Apache SOAP built-in Base64Serializer to serialize and deserialize the custom objects--in this case, an
Imagetype. Base64Serializer works with a byte-array object while serializing and deserializing. Therefore, the custom serializer'smarshallmethod must first convert the image into a byte array and pass the byte-array object to Base64Serializer'smarshallmethod. Similarly, in theunmarshallmethod, the custom deserializer must first call Base64Serializer'sunmarshallmethod, then with the resultingBeanvalue of type byte array, construct anImageobject from the byte array and return a newBeanofImagetype.
To accomplish that serialization and deserialization, the custom serializer can do either of the following:
- Extend from Base64Serializer and delegate to the
superclass methods, where required. - Instantiate a Bean64Serializer object and call the
marshallandunmarshallmethods on this object, where required.The following sample code demonstrates the
unmarshallmethod:public org.apache.soap.util.Bean unmarshall(java.lang.String inScopeEncStyle, org.apache.soap.util.xml.QName elementType, org.w3c.dom.Node src, org.apache.soap.util.xml.XMLJavaMappingRegistry xjmr, org.apache.soap.rpc.SOAPContext ctx) throws java.lang.IllegalArgumentException{ //Instantiate a Base64Serializer object. Base64Serializer srlizr = new Base64Serializer(); //Call its unmarshall method with the same parameters. Bean bn = srlizr.unmarshall(inScopeEncStyle, elementType, src, xjmr, ctx); //Take the byte array value from the resulting //bean from base64 unmarshall method. byte barr[] = (byte [])bn.value; //Create an image from the byte array. Image img = Toolkit.getDefaultToolkit().createImage(barr); //Create and return a new bean of type Image. return new Bean(java.awt.Image.class, img); } -
Deployment descriptor -- You must instruct the server-side SOAP runtime to use the custom serializer to serialize objects of type
java.awt.Image. Do so through the deployment descriptor.Suppose
soapattach.soap.serializers.ImageSerializeris the fully qualified class name of the custom serializer-deserializer. Add the following entry to the deployment descriptor:<isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:x="urn:xml-soap-j2eeca-soapattach-serverSaveImage" qname="x:Image" javaType="java.awt.Image" java2XMLClassName="soapattach.soap.serializers.ImageSerializer" xml2JavaClassName="soapattach.soap.serializers.ImageSerializer" /> - SOAP service client -- When the client encounters the
java.awt.Imagetype, it must declare to the SOAP runtime that the custom serializer is to be used. To make that declaration, create theSOAPMappingRegistryobject and set themapTypes.Here is the sample code:
//Create the registry and add mappings with the serializer info. theRegistry = new SOAPMappingRegistry(); //Create our new custom serializer object. mypkg.ImageSerializer serializer1 = new mypkg.ImageSerializer(); //Add the mapping to SOAP mapping registry. theRegistry.mapTypes(Constants.NS_URI_SOAP_ENC, new QName("urn:xml-soap-j2eeca-soapattach-serverSaveImage", "Image"), java.awt.Image.class, serializer1, serializer1);The client does not perform conversions for the parameters. As the method suggests, the client creates the
StringandImageobjects and sets them as the parameters on the SOAP call object.Here is the sample code:
//Create the parameter vector. Vector params = new Vector(); //Create and add the name parameter to the parameter vector. String fileName = new String("theNewFileName"); params.addElement(new Parameter("name",String.class,fileName, null)); //Create an Image object. Image img = Toolkit.getDefaultToolkit().getImage("c:\employeeimage.jpg"); //Add the image parameter to the parameter vector. params.addElement( new Parameter("image", java.awt.Image.class, img, null); return params;
When SOAP runtime sees the java.awt.Image type, it calls the custom serializer to serialize the Image object.
Consider the pros and cons of this approach:
Pros
- Only minimal customizations are required.
- The BLOB can occur at any place in the object graph.
- The BLOB can serve both types of JIO signatures: execute and
executeAPI.
Cons
- You must program the custom serializer. However, as the sample demonstrates, that's a straightforward process.
Approach 2: Use a Built-In MimePart Serializer
Apache SOAP contains a MimePart serializer that can serialize or deserialize objects of type InputStream, DataSource, DataHandler, and MimeBodyPart. Consequently, when a parameter is one of those four types, it does the following:
- Creates a Multipurpose Internet Mail Extensions (MIME) envelope and a MIME attachment with the object's value.
- Adds the attachment to the SOAP body.
To pass BLOBs to method parameters, you can make use of this built-in runtime support by adopting any of the preceding four data types. (Use DataHandler whenever possible.) Because the BLOBs are sent as attachments, this approach is also known as the attachment approach.
Here are the changes you must make to components:
- SOAP service -- Given that attachments are always deserialized as
DataHandlertypes, when the SOAP runtime at the server side receives an attachment, it calls theMimePartserializer to deserialize theDataHandlertype. The runtime looks for the methodexecuteAPIwith a signature (String,DataHandler), so you must provide a wrapper method as an overloaded implementation of the original method), like this:void executeAPI(String name, javax.activation.DataHandler dh)In this wrapper method, you convert
DataHandlerto the correct type, which isImage, then call the original method. See the following sample code:void executeAPI(String name, javax.activation.DataHandler dh) { //Create a ByteArrayInputStream from the DataHandler content. ByteArrayInputStream fis = (ByteArrayInputStream) dh.getContent(); //Find the available size of the ByteArrayInputstream //and read that into a byte array. int size = fis.available(); byte b[] = new byte[size]; fis.read(b); //Create the image from the byte array. Image img = Toolkit.getDefaultToolkit().createImage(b); //Call the original method with the name and image. executeAPI(name,img); } - Deployment descriptor -- You need not change the deployment descriptor. The implementation uses the default built-in support from Apache SOAP, so you need not add any additional mapping information to the deployment descriptor. Also, Apache SOAP uses only the method name at deployment and maps the parameter information at runtime; you need not specify the parameter types for any overloaded methods.
- SOAP service client -- Instead of directly using the
Imagetype, the client must convert the image (java.awt.Image) into aDataHandlertype and add theDataHandlerobject as the parameter to the method. The client must also set the parameters on the SOAP call object.Make the appropriate changes in the
populateInputData(...)method of the SOAP service client. The following sample code demonstrates thepopulateInputDatamethod:private Vector populateInputData() { // Create the parameter vector Vector params = new Vector(); //create and add the name parameter to the parameter vector String fileName = new String("theNewFileName"); params.addElement(new Parameter("name",String.class,fileName, null)); // create and add the image parameter to the parameters vector. DataSource ds = new ByteArrayDataSource(new File("c:\employeeimage.jpg"), "image/jpeg"); DataHandler dh = new DataHandler(ds); params.addElement(new Parameter("img", DataHandler.class, dh, null)); return params; }
Consider the pros and cons of this approach:
Pros
- Sending BLOBs as attachments is efficient.
- You need not customize or specify any serializers or deserializers.
Cons
- You cannot use this approach on the execute method variants because they expose input and output records only, but not individual parameters.
- This approach does not work in places where the BLOB is required deep in the object graph. The BLOB must be at the top of the graph for this approach to work.
- You must customize multiple areas: server, client code, and so forth.
Recommendation
Approach 1 offers the following advantages over Approach 2:
- The service client can use both types of JIO method signatures (
executeandexecuteAPI). - The BLOB can occur anywhere in the object graph-at the top, in the middle, or deep down in a leaf node.
- You need not customize the generated JIO.
Conclusion
With Sun ONE Connector Builder, you can efficiently build Web-service-ready connectors. By providing an end-to-end feature set (adapter, SOAP service endpoint, deployment descriptor, and generator of build scripts), Sun ONE Connector Builder helps you enable the resource adapter for SOAP interaction competently, seamlessly, and intuitively.
Moreover, through simple customizations of the generated code, you can enhance the generated services for SOAP-over-HTTPS interaction and attachment handling.
Explore the robustness and versatility of Sun ONE Connector Builder for yourself!
Acknowledgment
Thanks are due Vamsi Bondalapati for developing part of the sample code for this article.
About the Authors
Venkat Amirisetty, a senior staff engineer at Sun Microsystems, has been with Sun for six years. Currently, he s developing technology products for application integration and is the engineering lead for Sun ONE Connector Builder. Venkat works closely with the J2EE Connector Architecture 1.5 (JSR-112) Expert Group.
Marina Sum is a staff writer for Sun ONE for Developers. She has been writing for Sun for 14 years, mostly in the technical arena.