Create Web services using Apache Axis and Castor

本文介绍如何使用Apache Axis和Castor创建文档风格的Web服务,包括集成步骤和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Create Web services using Apache Axis and Castor

How to integrate Axis and Castor in a Document-style Web service client and server

Level: Intermediate

Kevin Gibbs, Software Engineer, IBM
Brian Goodman, IT Architect, IBM
Elias Torres, Software Engineer, IBM

30 Sep 2003

Recent work has pointed out the benefits of using Document-style Web services over RPC -- they're cleaner, more natural to XML, and facilitate object exchange. However, Document-style services can be less than straightforward to deploy using Axis, since Axis's data binding framework can be difficult to use, doesn't support some popular features of XML-Schema, and most importantly, lacks validation support. This article addresses those woes by providing a step-by-step tutorial which explains how to integrate Axis with the Castor data-binding framework, creating a best-of-both worlds Web service that combines the Web services prowess of Axis with the data-binding brawn of Castor.

Three years ago, when Web services entered the radar of the technology community, the Remote Procedure Call (RPC) interface was the common implementation. Many people looked at SOAP and started down the familiar path of technologies like RMI, CORBA, and their own custom hacks for exposing server-side data and functions -- complex, closed systems that defined how to make and receive a request in a rigorous, yet highly encoded fashion. The RPC style of encoding Web services, by providing an automatic method of exposing and calling methods, quickly became popular. Yet RPC-style encoding is ultimately a limiting, unnatural use of its underlying technology, XML. It represents a misuse of technology -- when simple XML alone, in a Document-style service, provides all the expressibility desired. Keeping technology standards in the vein of the most natural, straightforward solutions, like Document style, is the true spirit of Web services, where interfaces are exposed, back-end and middleware systems are hidden, and dynamic discovery, binding, and endless reuse abound.

This article shows how to use Castor XML binding to make Document-style Web services within an Apache Axis environment easier, cleaner, and more intuitive. This article begins with a discussion of Web service encoding methods and an explanation of why Castor and Axis together make a good solution. It provides instructions and explanations for all of the necessary steps to getting a Document-style Web service up and running -- everything from designing the schema and service to generating the service and client code. The article covers configuring Axis to use Castor and attempts to cover any "gotchas" a developer might encounter as they get their hands dirty.

The two faces of Document-style encoding

One challenge a developer encounters when working with Web services is that there are two invocation models: RPC and Document style. There are several good articles that address the differences of these two models in detail, but the following section will review these differences briefly to put the rest of the article in context.

RPC-style encoding seems attractive because it is conceptually the same as other implementations architects and developers have worked with over the years, such as CORBA or RMI. Document-style encoding piques interest, but RPC is easy and makes demonstrating the technology straightforward, and so frequently Document style receives short shrift. However, the minute development is past the proof-of-technology point, there is an immediate need to send real, complex objects over the wire. Sending Strings and Integers, maybe even Arrays, is okay for show, but the real world uses complex data structures and models to encode data. To handle this, SOAP RPC implementations support complex object serialization and deserialization. As long as the object complies with the Java Bean specification, the object could be turned into XML and handled transparently to the developer. This was wonderfully seductive -- in a few simple lines of code, real business data objects were sailing over the wire with little concern for underlying implementation.

But as it turns out, there are drawbacks to using complex objects over RPC. This approach often leads to integration issues. One implementation's serialization might not match another's deserialization, since the Java Bean to XML SOAP encoding process is ambiguous and not well-defined. Suddenly open technology comes to a screeching halt -- an Apache SOAP service had trouble working with .NET because of discrepancies in their implementations, and thus drove the need to keep services more open.

Document-style Web services offer a satisfying mix of well-defined structures and interoperability. This is achieved through standard XML-Schemas for defining complex objects. In contrast to SOAP encoding, XML-Schema is a rigorous and well-understood standard for defining structures. XML-Schemas provide a great deal of flexibility in defining complex structures while ensuring all the promises of Web services -- language, platform, environment, and transport-independent with a common application programming interface.

Document style, which gains all the benefits of XML-Schema, seems to be the solution to all your Web service headaches. However, Document style has some trade-offs. One of the trade-off problems the programmer is faced with is increased complexity. Suddenly, the developer has the arduous task of parsing an XML document and performing the necessary transformations to populate other data beans or method requests with the incoming data. This is true both for the server and client. For the brave of heart, this means writing a custom SAX handler. And SAX handlers aren't known for being particularly user friendly or easy to maintain.

Solution in the making: Apache Axis

Apache Axis is one of the most popular Web services toolkits out there. Axis supports both RPC and Document-style services, and so it would seem to be the right starting point for a Document-style service. As with any Document-style service, you still have the task of handling the incoming XML data somehow. Axis includes a handy tool to help solve this arduous task, called WSDL2Java. WSDL2Java can generate both code stubs for the client and server for your methods, and actual beans to model your data from the model defined in the XML-Schema. WSDL2Java will then populate these beans automatically from the XML. This process in general is called data binding, and it's one of the mainstays of the movement behind XML-Schema. WSDL2Java can be a little quirky, but in general it helps get things rolling. Clearly a tool like this is very useful to have, but unfortunately it is not the end-all for client stub generation -- it faces some real issues:

  • WSDL2Java suffers from the common trap most technologies in the space have fallen into, which is playing catch-up with schema support. Writing a tool that can correctly and completely handle the highly complex XML-Schema standard is no small task. It alone represents an effort potentially as difficult as Axis itself. WSDL2Java is noticeably lacking in this area, having buggy or incomplete support for many XML-Schema features. Examples of this are support for attribute groups and choice groups, but these are of course changing, as work on WSDL2Java continues. The point, however, is that writing and maintaining such a complex piece of code isn't the area that Axis is concentrating on, and WSDL2Java will likely continually be playing a game of catch-up to meet the functionality of stand-alone data-binding solutions.
  • Another problem, related to the first, is that the code that WSDL2Java generates lacks XML validation capability. When you begin to work with XML documents, validation is important, and the XML-Schemas allow validation to occur automatically. However, WSDL2Java doesn't have a facility to do this.
  • The interface to serialization to and from strings is not intuitive. If you are using WSDL2Java's data binding code for your XML objects, chances are, you'll need to encode or decode the objects back to an XML string at some point. While this is possible with WSDL2Java's generated objects, it's not an easy task. A large framework has to be set up, and it can cause a lot of headaches, for what seems to be a simple task.

Another reason the development community has some mixed reactions and confusion about using Apache Axis is because it has grown with it through its beta phases. The quirky workarounds and configuration mechanisms many learned have changed from version to version. This is just a reality of a developing body of code. Initially, when Axis was first being produced, the primary focus was RPC-style Web services. As such the support for RPC services is greater, and its interfaces are more stable and well-known to developers. Until recently, Document-style documentation, examples, and sample configurations for Axis have been limited. With the advent of multiple internal configuration options for using Document style, such as wrapped, document, or message, developers have a few more decisions and complexities to go through in setting up their own Document-style service. While these configuration difficulties are not a great hurdle, by any means, working with RPC has traditionally been the quick, easy way to get a Web service off the ground. Certainly as Axis continues to develop, this will resolve itself. But for the here and now, with the explanations and step by step instructions in this article, working with Document-style services is about to get much easier.

Axis and Castor: The best of both

Castor is a stand-alone XML data binding package that provides a mapping from XML-Schema to Java objects. These Java objects look and feel like beans, but can be marshaled and un-marshaled to XML strings or streams, and importantly, validated against their original schema. Castor is an open-source effort that compliments the open technologies of Web services. It is backed by a very active development community, and it has in turn generated a lot of attention and Web content to help developers leverage the technology effectively.

We will use Castor where Apache Axis and WSDL2Java fall short, to build a solution of best practices on all levels. We'll gain the benefits of an easy, intuitive data binding interface with a more fully supported schema implementation, while still leveraging all the Web services capabilities of the Axis framework. Figure 1 shows the relationship between Axis and Castor.


Figure 1. Relationship between Axis and Castor in our solution
Diagram of relationship between Axis, Castor in our solution



What you need to get started

This article is intended for intermediate Java developers familiar with Web services and the various technologies involved in creating and deploying them. If you can write a basic WSDL and deploy a service with Axis, then you should be ready for everything in this article. Likewise, the article assumes some knowledge of XML and Schema. All the code was developed, tested and deployed using WebSphere Studio Application Developer and WebSphere Application Server versions 4.0.4 and 4.0.6. Other development and deployment environments or libraries could very well be substituted to achieve similar effect, but we'll concentrate on these environments here.

Getting the latest Axis and Castor

To build and run this project, you'll need Apache Axis and Exolab's Castor. Castor is available at http://www.castor.org. Instructions for download and installation are available there, which are very straightforward. Grabbing Apache Axis is a little more difficult, but not too much. At the time of this article's writing, the code needed to make Axis interoperate properly with Castor is not yet in a released beta, and is only found in CVS. By the time you read this, the functionality will very likely be in the latest released JARs. However, until it is, the best way to get the latest Axis with the org.apache.axis.ser.CastorSerializer and Deserializer code necessary for this project is to grab it from CVS or download a nightly build, both of which are available at http://ws.apache.org/axis/cvs.html. Follow the instructions there to download and build a copy of Apache Axis, which is easier than it seems, thanks to the Apache Ant build architecture.

Getting Axis off the ground in WebSphere

Our sample environment for this project is WebSphere Application Server V4.0.4. Getting Axis set up with WebSphere or another Java container isn't too difficult, but it's important to get the steps right. The Apache Axis documentation includes a guide for doing so (see Resources), or in /xml-axis/java/docs/install.html in your local CVS checkout or nightly.

If you're using WebSphere Application Studio V5.0, you may run into a "gotcha" on JAR conflicts between what's installed on WebSphere Application Developer V5 and what you are trying to use with Apache Axis. If you run into these problems, try this quick fix: in your Server Configuration for your test server, set the System Property (-D property) for "com.ibm.ws.classloader.warDelegationMode" to false in the Environment Options pane. You'll find this under Server perspective > the server you are using for our example > Environment Options > System Properties > Add....




Define the schema and the service

The input that you'll need to developing your Web service is a schema to represent the data you'll be using and a service description to delineate your methods that you would like to expose. Describing how to write an XML-Schema or a WSDL is beyond the scope of this article, but documentation on how to do so abounds elsewhere.

For the purpose of this article, we'll create an example service: the StockQuote service.

Defining the schema for your data: StockQuote.xsd

The first thing that your service needs is a description in XML-Schema of your data model. In Listing 1 is the simple data model that you'll use in your StockQuote service, which should be self-explanatory.


Listing 1. Data Model for the StockQuote service form StockQuote.xsd

<xsd:element name="quote">
     <xsd:complexType>
          <xsd:sequence>
               <xsd:element name="symbol" type="xsd:string"/>
               <xsd:element name="volume" type="xsd:integer"/>
               <xsd:element name="lastTrade" type="lastTradeType"/>
               <xsd:element name="change" type="changeType"/>
          </xsd:sequence>
     </xsd:complexType>
</xsd:element>

<xsd:complexType name="changeType">
     <xsd:sequence>
          <xsd:element name="dollar" type="xsd:float"/>
          <xsd:element name="percent" type="xsd:float"/>
          <xsd:element name="positive" type="xsd:boolean"/>
     </xsd:sequence>
</xsd:complexType>

<xsd:complexType name="lastTradeType">
     <xsd:sequence>
          <xsd:element name="price" type="xsd:float"/>
          <xsd:element name="date" type="xsd:long"/>
     </xsd:sequence>
</xsd:complexType>

In addition to defining the elements and complexTypes that represent your data model, you must also define the representation of the input/output messages of your interface. These are the methods that your Web service will expose, and that you will point to in the WSDL, which you'll create in the next step, shown in Listing 2.


Listing 2. Method Signatures for the StockQuote service from StockQuote.xsd

<xsd:element name="getStockQuote">
	<xsd:complexType>
		<xsd:sequence>
			<xsd:element name="symbol" type="xsd:string" />
		</xsd:sequence>
	</xsd:complexType>
</xsd:element>
<xsd:element name="getStockQuoteResponse">
	<xsd:complexType>
		<xsd:sequence>
			<xsd:element ref="quote" />
		</xsd:sequence>
	</xsd:complexType>
</xsd:element>

Defining the WSDL for your service: the StockQuote WSDL

Next you must build a WSDL file for your service. You can do this in the standard fashion here, but with a few important notations. First, notice the bold highlight in Listing 3. In standard fashion you define the "types" namespace prefix with the URI associated to your data model. Next, you import the desired stand-alone schema file, StockQuote.xsd, into the WSDL, and finally you make use of the "types" namespace in the message declarations in the WSDL. Why is it important that we do all of this? Because Castor does not natively support input of <xsd:schema> nodes embedded in files, in this case from the <types> section of a WSDL file. We've found that using an external file like this to define your data model and methods is not only cleaner and easier to maintain, but it allows Castor and other tools to interact directly with your schemas as a file.


Listing 3. WSDL file for the StockQuote service

 <definitions
  targetNamespace="http://w3.ibm.com/schemas/services/2002/11/15/stockquote/wsdl"
  xmlns="http://schemas.xmlsoap.org/wsdl/"
  xmlns:tns="http://w3.ibm.com/schemas/services/2002/11/15/stockquote/wsdl"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
  xmlns:types="http://w3.ibm.com/schemas/services/2002/11/15/stockquote">
   <types>
     <xsd:schema elementFormDefault="qualified"
        targetNamespace=
        "http://w3.ibm.com/schemas/services/2002/11/15/stockquote/wsdl/importtypes">
          <import namespace=
                "http://w3.ibm.com/schemas/services/2002/11/15/stockquote"
             location="StockQuote.xsd" />
     </xsd:schema>
   </types>
   <message name="getStockQuoteReq">
     <part name="parameters" element="types:getStockQuote" />
   </message>
   <message name="getStockQuoteResp">
     <part name="parameters" element="types:getStockQuoteResponse" />
   </message>
   <portType name="StockQuotePortType">
        <operation name="getStockQuote">
             <input message="tns:getStockQuoteReq" />
             <output message="tns:getStockQuoteResp" />
        </operation>
   </portType>
      . . .
 

The only other thing of note here is setting up the WSDL to use Document-style encoding. Notice the bold highlight in Listing 4, which is the continuation of the WSDL. These sections are what will tell the WSDL, and correspondingly, WSDL2Java, that you are using a Document-style Web service, and that you should generate document (non-RPC) -style service requests.


Listing 4. WSDL file for the StockQuote service, continued

      <binding name="StockQuoteSOAPBinding" type="tns:StockQuotePortType">
           <soap:binding style="document"
              transport="http://schemas.xmlsoap.org/soap/http" />
           <operation name="getStockQuote">
                <soap:operation style="document" soapAction="getStockQuote" />
                <input>
                     <soap:body use="literal" />
                </input>
                <output>
                     <soap:body use="literal" />
                </output>
           </operation>
      </binding>
      <service name="StockQuoteService">
           <port name="StockQuoteSOAPPort" binding="tns:StockQuoteSOAPBinding">
                <soap:address
                   location=
                   "http://localhost:9081/AxisWeb/services/StockQuoteSOAPPort" />
           </port>
      </service>
</definitions>




Generate required code and stubs

Now that you've got your service definition and schema prepared, you're ready to get your hands dirty with Axis and Castor. Both Axis and Castor require some code generation, and you'll be overlaying the generation of Castor's code on top of Axis's to get this "best of both worlds" solution of Axis's Web Service client and server code and Castor's data binding. This first section will show you how to generate the code, and the next section will show you how to reconfigure the generating mappings to get the interoperability you desire.

Building client and service stubs using WSDL2Java

WSDL2Java generates Java classes for data binding of the objects in your data model, client and server stub code for connecting to your methods, and service binding information for the Axis server. We are going to use the latter two parts, the stubs and service binding, and replace the data model code with the code generated by Castor. But to do this, we must first run WSDL2Java to generate stubs and service binding we will need.

To generate the required files with WSDL2Java, run the following command:

%java org.apache.axis.wsdl.WSDL2Java -s "Web Content/StockQuoteService.wsdl"
--NStoPkg http://w3.ibm.com/schemas/services/2002/11/15/stockquote/wsdl=com.ibm.w3.services.stockquote
--NStoPkg http://w3.ibm.com/schemas/services/2002/11/15/stockquote=com.ibm.w3.services.stockquote
-o "Java Source"

The command above will generate the files shown in Listing 5.


Listing 5. WSDL2Java generated files

 StockQuoteSOAPBindingImpl.java // a server-side service implementation template class,

 StockQuotePortType.java        // a server portType interface,
 StockQuoteSOAPBindingStub.java // a server stub class, 
                                   where we'll write the getStockQuote method,
 StockQuoteService.java         // a client-side service interface,
 StockQuoteServiceLocator.java  // a client-side service implementation (the locator),

 deploy.wsdd                    // the service bindings for the Axis server,
 undeploy.wsdd

 _quote.java                    // plus the classes to represent the data model,
 ChangeType.java                // which we will be replacing with the ones generated
 LastTradeType.java             // by Castor.
 . . .

When you ran the command to generate the files with WSDL2Java, you provided several arguments. The NStoPkg arguments specify the Java packages you want to use for the different namespaces used in your StockQuote.xsd file, automatically included from the WSDL file by WSDL2Java. You'll use the same mapping when you run Castor to generate the data model classes, in the next step. There is an optional extra parameter to WSDL2Java which we didn't specify in this article, and that is to not use the wrapped style. This option can be specified by adding "-W" to the command line above. What does the wrapped style mean, in WSDL2Java? It controls how WSDL2Java generates method clients and stubs. For a Document-style service, specifying that you don't want to use wrapped with the -W option would map to a method like this being generated in the StockQuoteSOAPBindingStub class:

public GetStockQuoteResponse getStockQuote(GetStockQuote gsq)

In other words, the entire <GetStockQuote> element, defined in your StockQuote.xsd file and shown in Listing 2, would be handed to your method as a single bean with three fields inside it. On the other hand, for a standard wrapped style service, which is what you are generating in this example, it would map to a method like this:

public Quote getStockQuote(String symbol)

This article will use the wrapped style, as we think it's a little easier to read, and it will save you from writing a few lines of extra code.

Generating a data binding with Castor

Now we're ready to continue and generate the data model data binding with Castor, to replace the one that WSDL2Java created. Since Castor is not Web service-specific, it works with XSD files directly, and not with WSDL files. Hence, you pass it the StockQuote.xsd file, with the following command:

%java org.exolab.castor.builder.SourceGenerator -i "Web Content/StockQuote.xsd"
-package com.ibm.w3.services.stockquote
-nomarshall
-dest "Java Source"
-f

You pass Castor a few arguments on the command line. The -package argument is the analog of WSDL2Java's NStoPkg argument. The -nomarshall argument tells Castor to not generate the marshalling framework methods (marshall, unmarshall, validate) in each bean that it generates. In your example, these methods are not needed because the Castor Serializer and Deserializer for Axis is using the Castor Marshaller and Unmarshaller classes directly, which accomplishes the exact same purpose, and so for clarity we've turned them off. If you plan to use the objects as part of a larger system, it might make sense to turn them back on. The -f arguments just tells Castor to overwrite any existing files without prompting. If you've been paying attention, you might have noticed that this could overwrite some of the data model beans that WSDL2Java created in the previous step. Not to worry, however, because you won't be using those files, so this isn't a problem.

The remaining options for Castor are specified in the castorbuilder.properties file. Most of these settings are not important, but there is one important setting: the javaclassmapping setting.

# Java class mapping of <xsd:element>'s and <xsd:complexType>'s
#
org.exolab.castor.builder.javaclassmapping=element

This setting determines how Castor generates classes from your schema. Depending on how you wrote your schema, you may want to use element or type. The difference between the two is that the element method creates classes for all elements whose type is a complexType. Abstract classes are created for all top-level <complexTypes>. Any elements in the schema whose type is a top-level <complexType> will have a class created for them that extends the <complexType's> abstract class. Classes are not created for elements whose type is a <simpleType> -- instead, underlying Java types are used directly. The other option, the type method, behaves differently. Instead of creating classes for each element, which extend the classes created for <complexTypes>, the type option creates classes for all top-level <complexTypes> and all inlined anonymous <complexTypes>. Elements are then formulated as instances of those classes, without separate classes of their own.

These two options are a little complicated to explain, but one option tends to be the natural one for each schema, depending on how your schema is defined. So, for your own schema, experiment with both methods until you come up with classes that you like. For this StockQuote.xsd example, we found that the element style worked well, and so we used it for generating example data model beans here.




Configure Axis and Castor to work together

It's time for the nitty-gritty: getting Axis to use Castor's generated classes instead of its own. This task is the most critical point of the article, and it shows you how to do the "tough" work of getting Axis to use Castor's classes in both the client and server code stubs. But don't worry -- with these step by step instructions, it won't be tough at all. To get all this off the ground involves changes to the Axis server stubs classes, and to the Axis-generated .wsdd file.

Modifying the server stubs to use Castor classes

To make the stubs use Castor classes, you simply modify the classnames in the stub files. As we have already mentioned, although frequently Castor and WSDL2Java will generate the same classnames, resulting in stubs with the same classnames, this is not always the case. Thus, it is important to check the WSDL2Java client and server stub code to make sure that they are set to return the proper classname for your Castor-generated objects.

Looking at Listing 5 again, notice that two files, StockQuotePortType.java and StockQuoteSOAPBindingStub.java are listed as being for the server. For our purposes, we'll have to modify both of these files. The only modification that will be necessary here is checking all of the classnames, and making sure that they point to the Castor-generated classes, rather than the WSDL2Java-generated ones.

As it turns out, in our example, only one class name has changed: _quote. The class _quote, generated by WSDL2Java, has changed to Quote in Castor. They are both still in the same package, since we generated them that way, but we must change the class name in each place that it is referenced.

The change for the file StockQuotePortType.java, then, is that the line
public com.ibm.w3.services.stockquote._quote getStockQuote(java.lang.String symbol) throws java.rmi.RemoteException;

becomes:
public com.ibm.w3.services.stockquote.Quote getStockQuote(java.lang.String symbol) throws java.rmi.RemoteException;

For the file StockQuoteSOAPBindingStub.java, we must change a similar line,
public com.ibm.w3.services.stockquote._quote getStockQuote(java.lang.String symbol) throws java.rmi.RemoteException {...

into
public com.ibm.w3.services.stockquote.Quote getStockQuote(java.lang.String symbol) throws java.rmi.RemoteException {...

Now all references in the server code are pointing to the Castor classes. That means we're ready to modify the deploy.wsdd file to make use of these classes and the Castor configuration.

Modifying and deploying the server-config.wsdd file

The second task is modifying the deploy.wsdd. Let's start with an overview of that file, with the sections you'll need to modify highlighted, in Listing 6.


Listing 6. Modified deploy.wsdd for StockQuote service

<service name="StockQuoteSOAPPort" provider="java:RPC" style="wrapped" use="literal">
    <parameter name="wsdlTargetNamespace"
          value="http://w3.ibm.com/schemas/services/2002/11/15/stockquote/wsdl"/>
    . . .

    <typeMapping
      xmlns:ns="http://w3.ibm.com/schemas/services/2002/11/15/stockquote"
      qname="ns:changeType"
      type="java:com.ibm.w3.services.stockquote.ChangeType"
      serializer="org.apache.axis.encoding.ser.castor.CastorSerializerFactory"
      deserializer="org.apache.axis.encoding.ser.castor.CastorDeserializerFactory"
      encodingStyle=""
    />
    <typeMapping
      xmlns:ns="http://w3.ibm.com/schemas/services/2002/11/15/stockquote"
      qname="ns:lastTradeType"
      type="java:com.ibm.w3.services.stockquote.LastTradeType"
      serializer="org.apache.axis.encoding.ser.castor.CastorSerializerFactory"
      deserializer="org.apache.axis.encoding.ser.castor.CastorDeserializerFactory"
      encodingStyle=""
    />
    <typeMapping
      xmlns:ns="http://w3.ibm.com/schemas/services/2002/11/15/stockquote"
      qname="ns:quote"
      type="java:com.ibm.w3.services.stockquote.Quote"
      serializer="org.apache.axis.encoding.ser.castor.CastorSerializerFactory"
      deserializer="org.apache.axis.encoding.ser.castor.CastorDeserializerFactory"
      encodingStyle=""
    />
  </service>

There are two types of changes we have to make to this file, and one "gotcha" to watch out for. All the changes are on the <typeMapping> elements. The first change is to modify the classnames listed under the type attribute to use the Castor-generated classnames instead of the WSDL2Java classnames, if they differ. In our example, for the first two mappings, ns:changeType and ns:lastTradeType, the classnames remained the same in Castor, so no change was necessary. However, for the ns:quote class, the class name did change, from WSDL2Java's _quote to Castor's Quote.

The second change to make is to change all of the serializers and deserializers in the <typeMappings> to org.apache.axis.encoding.ser.castor.CastorSerializerFactory and org.apache.axis.encoding.ser.castor.CastorDeserializerFactory, respectively. This tells Axis to use the special, Castor-specific classes for serializing and deserializing the incoming XML for these types, rather than the default org.apache.axis.encoding.ser.BeanSerializerFactory and org.apache.axis.encoding.ser.BeanDeserializerFactory classes. These classes are the classes which are included in the latest Axis 1.1 CVS revision, as of this writing, which is exactly why we had you grab the CVS version of Axis in the "Getting the latest Axis and Castor" step.

There's one "gotcha" to watch out for here, and we've highlighted a place where it came up for us in the code above. The "gotcha" has to deal with how Axis and WSDL2Java deals with reading and writing XML. In our tests, we found that WSDL2Java occasionally generated invalid XML by misplacing a few '<' and '>' in the output. The qname attribute in the final <typeMapping> element above was one such place. The generated value for the qname attribute was "ns:>quote", which clearly isn't valid XML, since '>' is a protected character. In any case, deleting this stray '>' solved all the problems, but keep an eye open for other stray characters like this one.

Deploying the service using the deploy.wsdd file
Now that you've got your deploy.wsdd modified and ready to go, you need to deploy it on the server. You can do this by copying the <service> element from the deploy.wsdd file to the WEB-INF/server-config.wsdd file, but you can also use Axis's convenient deployment utility to do it for you. This method also ensures that the server-config.wsdd file already exists, creating it if it doesn't, and does some error checking, so it's the right way to go.

To run the deployment utility, make sure your working directory is set to the root Axis directory, in our case WEB-INF, and run the following command:
%java org.apache.axis.utils.Admin server classes/com/ibm/w3/services/stockquote/deploy.wsdd

If the command executes successfully, no errors will be returned, and your service has been deployed. Now, simply restart your server, and you should be ready to go.

Similarly, if you at some point later would like to undeploy your test service, run the command:
%java org.apache.axis.utils.Admin server classes/com/ibm/w3/services/stockquote/undeploy.wsdd

Test out the service on Axis

Now that you've got all the code built, integrated Axis and Castor, and deployed the service in the .wsdd file, it's time to test everything out. Test your install out by pointing it to the endpoint you defined in the .wsdd file, in this case <context root>/services/StockQuoteSOAPPort. You should see something like Figure 2.


Figure 2. Axis running our StockQuote service
Axis running our StockQuote service

If you can't get this message to display, then something went wrong in your set-up of Axis or the StockQuote service, so look over your configuration and the above steps again until everything's working as it should be.

If you can get this message to display, then you're ready to write some code to make the getStockQuote method actually do something.




Write the methods

Everything's up and working now, but your server still doesn't do anything -- you haven't implemented the getStockQuote method yet. With everything set up, doing this is remarkably simple, however. All you need to do is fill in the methods in StockQuoteSOAPBindingImpl, which correspond one-to-one to the method signatures you created in Listing 2.

Writing the getStockQuote method

Since this is a sample Web service, we're not really going to do much here. But this sample code points out just how easy it is to work with the data binding code generated by Castor and filled in by Axis. Here's our sample code for the getStockQuote method, in Listing 7.


Listing 7. Sample getStockQuote method, in StockQuoteSOAPBindingImpl.java

public class StockQuoteSOAPBindingImpl implements
            com.ibm.w3.services.stockquote.StockQuotePortType {

    public com.ibm.w3.services.stockquote.Quote getStockQuote(java.lang.String symbol)
              throws java.rmi.RemoteException {
        Quote quote = new Quote();
        quote.setVolume(979012312);
        quote.setSymbol(symbol);

        Change change = new Change();
        change.setDollar(678F);
        change.setPercent(300F);
        change.setPositive(true);
        quote.setChange(change);

        LastTrade lastTrade = new LastTrade();
        lastTrade.setDate(new java.util.Date().getTime());
        lastTrade.setPrice(678F);
        quote.setLastTrade(lastTrade);

        return quote;
    }
}

What's important to note is that the objects used here are the same ones used on the client, and have already been validated according to their schema by Castor, so any incoming data will be safe to use. If the incoming data doesn't validate, Castor will throw an exception before your method gets called. Similarly, you get the benefits of validation for data you return, as well -- if the data in your returned bean doesn't validate, Castor will throw an exception when Axis attempts to serialize the outgoing bean.

Additionally, although our beans are pretty simple here -- just the Quote, Change, and LastTrade objects -- Castor handles quite admirably much more complicated XML-Schema data models that WSDL2Java tends to have problems with.




Build the client

Now that you've got everything running with Axis and Castor on the server side, you're ready to build a client.

Using the dynamic Axis Web services client and our knowledge of Castor, we might make a first attempt like that shown in Listing 8.


Listing 8. First attempt at a client for the StockQuote service

     Service service = new Service();
     Call call = (Call) service.createCall();
     call.setTargetEndpointAddress(new URL(ENDPOINT));

     GetStockQuote request = new GetStockQuote();
     request.setSymbol("IBM");

     StringWriter sw = new StringWriter();

     Marshaller mar = new Marshaller(sw);
     mar.marshal(request);

     String xml = sw.getBuffer().toString();
     Document doc = XMLUtils.newDocument(new ByteArrayInputStream(xml.getBytes()));
     SOAPBodyElement[] input = new SOAPBodyElement[1];
     input[0] = new SOAPBodyElement(doc.getDocumentElement());

     Vector elems = (Vector) call.invoke(input);

This is OK, but it's not too automatic. However, with a little tweaking to the Axis-generated client stubs, we might be able to do better, and have a client that does all the set-up automatically.

Modifying the client stubs to use Castor classes

To make the stubs use Castor classes, simply modify the classnames in the stub files, just like in the server stubs -- although, frequently Castor and WSDL2Java will generate the same classnames, thus resulting in stubs with the same classnames. This is not always the case, so it is important to check and correct.

The file that you'll need to modify is StockQuoteSOAPBindingStub.java. In this case, WSDL2Java generated the class for the Quote element as _quote, but Castor generates it as Quote, so you need to change those all over. Rather than listing every place it is referenced and every line that needs to be changed, which is repetitive and not so useful to read over, we'll leave it up to you to just go through the file and change all references to the class com.ibm.w3.services.stockquote._quote to com.ibm.w3.services.stockquote.Quote.

Modifying the client to use Castor serializers rather than Axis serializers

Now, the crucial change: Modifying the client to use the Castor-specific serializer and deserializer classes, rather than the BeanSerializers that the WSDL2Java-generated code uses by default. In the server, we were able to modify a configuration file, deploy.wsdd, to tell it what class to use to do the serialization and deserialization. The client, unfortunately, doesn't use such a configuration file, so we have to modify it in the code. However, it's a very simple modification, and it's clear that it's doing just what the modification to deploy.wsdd does on the server side.

The first thing you need to modify to get the client to use the Castor serializer and deserializer classes is to add them to the list of the possible serializers, created in the constructor public StockQuoteSOAPBindingStub(javax.xml.rpc.Service service) of StockQuoteSOAPBindingStub.java.


Listing 9. Modified constructor in StockQuoteSOAPBindingStub.java

public StockQuoteSOAPBindingStub(javax.xml.rpc.Service service)
                                             throws org.apache.axis.AxisFault {
    if (service == null) {
        super.service = new org.apache.axis.client.Service();
    } else {
        super.service = service;
    }
        java.lang.Class cls;
        javax.xml.namespace.QName qName;
        java.lang.Class castorsf =
        org.apache.axis.encoding.ser.castor.CastorSerializerFactory.class;
       java.lang.Class castordf =
        org.apache.axis.encoding.ser.castor.CastorDeserializerFactory.class;
        java.lang.Class beansf = 
        org.apache.axis.encoding.ser.BeanSerializerFactory.class;
        java.lang.Class beandf = 
        org.apache.axis.encoding.ser.BeanDeserializerFactory.class;
        java.lang.Class enumsf = 
        org.apache.axis.encoding.ser.EnumSerializerFactory.class;
        java.lang.Class enumdf = 
        org.apache.axis.encoding.ser.EnumDeserializerFactory.class;
        java.lang.Class arraysf = 
        org.apache.axis.encoding.ser.ArraySerializerFactory.class;
        java.lang.Class arraydf = 
        org.apache.axis.encoding.ser.ArrayDeserializerFactory.class;
        java.lang.Class simplesf = 
        org.apache.axis.encoding.ser.SimpleSerializerFactory.class;
        java.lang.Class simpledf = 
        org.apache.axis.encoding.ser.SimpleDeserializerFactory.class;

        . . .

Listing 9 shows the modification to make to the constructor in bold. The change is relatively self-explanatory: it adds the CastorSerializerFactory.class and CastorDeserializerFactory.class as options to be used by the code that follows, which defines the serializer and deserializers to use for incoming and outgoing objects.

The final step in updating the client is to change all the occurrences of cachedSerFactories.add(beansf) and cachedDeserFactories.add(beandf) to use Castor instead of Axis's BeanSerializer in the above constructor. This is equivalent to the change we made in deploy.wsdd, where we listed the Castor serializing and deserializing classes instead of the Axis's BeanSerializer. You'll want to change each occurrence of beansf and beandf to castorsf and castordf where you are talking about your objects, which in this case, is all occurrences.

For example, one of the changed code blocks will look like Listing 10, with the changes in bold:


Listing 10. Sample modified block in StockQuoteSOAPBindingStub.java

    qName = 
    new javax.xml.namespace.QName(
    "http://w3.ibm.com/schemas/services/2002/11/15/stockquote", "lastTradeType");
    cachedSerQNames.add(qName);
    cls = com.ibm.w3.services.stockquote.LastTradeType.class;
    cachedSerClasses.add(cls);
    cachedSerFactories.add(castorsf);
    cachedDeserFactories.add(castordf);

Make this change on each remaining block in the constructor, and then you're done: the Axis generated client is now using Castor serialization and data binding.

Trying the generated client

Now, you can write client code that looks like Listing 11.


Listing 11. The final StockQuote client

     StockQuoteService service = new StockQuoteServiceLocator();

     StockQuotePortType port = service.getStockQuoteSOAPPort();

     Quote quote = port.getStockQuote("IBM");

     System.out.println(quote.getVolume());

It's safer, more automatic, and it's easy to work with. Moreover, you've now got a Web service using Axis for communication and Castor for validation and data binding, end to end.




Axis and Castor: Document-style fun for the whole family

As you can see here, integrating Document-style services, Castor, and Axis isn't terribly difficult, just a bit complicated to set up. But once you're off the ground, you've got a Web service that gains all the flexibility and clarity of Document-style, the robust Web services support of Axis, and the validation and data binding prowess of Castor.

Once you've got all this set up, there are a lot of other interesting directions you can go in. For instance, in just a few more lines of code, you can have your server-side Castor objects marshall themselves into an SQL database, using Castor JDO. You can also use the regular expression and validation support of Castor to clean up Web service data so that your service and client have less room for potential bugs in their data. Look for more information on these topics in future articles building on integrating Castor and Axis.





Download



Back to top


Resources




About the authors

Kevin Gibbs is a Software Engineer with IBM's Advanced Internet Technologies in Cambridge, MA. He previously worked on the SashXB for Linux scripting environment, and currently investigates various Internet technologies, including Web services and blogging architectures. You can reach Kevin at kagibbs at us.ibm.com.


 

Brian Goodman is an IT Architect focusing on expertise, communities and collaboration on the IBM Intranet. You can reach Brian at bgoodman at us.ibm.com.


 

Elias Torres is a Software Engineer with IBM's Advanced Internet Technologies in Cambridge, MA. He has worked on the Sash scripting environment and explores technologies such as blogging and their practical benefit to corporate environments. You can reach Elias at eliast at us.ibm.com.

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值