A Struts/Web Service Sample ApplicationNow it's time to move from theory to practice. This sample application is intended to illustrate a collection of what we consider to be best practices with regard to using Struts with Web Services. This application is developed using Tomcat as our servlet/JSP container and Apache Axis as a Web Services server. Basic configuration information is provided directly in the chapter as the need for it arises. Because Web Service implementations differ in the specifics of how they implement the SOAP protocol, it's possible that the code you develop here might need modification before it works with any other Web Services serverfor example, a .NET implementation. On the other hand, things might work with little or no modification. One of the benefits of using Axis in the sample application is that its adoption rate is accelerating and it's likely that if you use Struts to access Web Service, that service might be Axis-based. To demonstrate this application, you'll walk through all the steps required to build similar applications on your own. The Application: Updating Customer InformationIn this basic application, a simple form is provided to allow information about a customer to be updated. Like any Struts form, the information is populated into a form bean when it's submitted. The form bean is then passed to an Action class for processing. After processing, an ActionForward is called to indicate which JSP file to display the results with. All this processing is very basic and should be ingrained in your memory by now. The changes related to integrating with the Web Service are isolated in the Action class. The Action class must update the customer information by calling the Web Service that manages the customer information. Figure 19.1 shows the form used to enter the information. Figure 19.1. The sample Web Services application /strutsWS/index.jsp .
When you submit the form, the address of the customer changes. That's all. No magic, smoke, or mirrors. The Web Service that generates this new address simply chooses randomly between three hard-coded addresses and returns one. The purpose of this application isn't to be fancy; it's to provide a working application that demonstrates good design fundamentals that you can copy and reuse. In reviewing this sample application, you first go over configuring the system and building the application. Following that, you look through the application code itself. Be forewarned that although we've done our best to make the configuration and build steps in this chapter accurate, updated versions of Axis, Tomcat, or Struts might make these directions obsolete. But even if this is this case, the overall approach should still be instructive. Installing the Sample Application FileThe sample application for this chapter is provided on the companion CD-ROM for this book. It's contained in the file strutsWS.zip . To install it, simply extract it into a directory where you can work on it. For the rest of the chapter, directories that are a part of the build process are referred to using their parameter names as defined in the Ant build.xml file that's provided with the sample application. After the sample application is installed, you can move to the next step and begin the process of building, deploying, and running the sample application. Note Although most of the following steps have already been completed in the sample application, this chapter proceeds as if they haven't. This is done so that you can understand how the application was built. Please follow all the steps closely!
Axis 1.0: A Flexible, Extensible Web Services FrameworkWhen the first Apache Axis 1.0 release candidate was announced, the Axis team posted an email to the axis- user email list server. In it, they said that Axis
Axis is freely available from its Web site (part of the Apache XML project at http://xml.apache.org/axis). A copy of Axis 1.0 is also included on the companion CD-ROM for this book. Our sample application uses a number of features of Axis, including
The Web Services Server: Installing the Axis ServerIn a Web Services application, there are both client and server components . In the sample application, both of these are based on Axis. The client portion of the sample application is the Struts application. The server portion of the application is based on Axis as well and is deployed in Tomcat as a separate Web application. As a result, you have two Web applications: the Struts application acting as the Web Service client and the Axis server application. The 1.0 release of Axis is included on the companion CD-ROM for this book. Begin the installation process by unzipping it into a convenient directory. When that's complete, the first major step is to install the Axis server application (without the sample application code to begin with) and validate that it's working correctly. Following are the steps to accomplish this first step:
Listing 19.1 presents the output from the Happy Axis page on my system at the time of this writing. (Note: All the formatting has been removed, but you can still look at the text to see how I have my system configured.) If you have any problems making the Happy Axis page happy, check the locations of each library ( .jar ) file in Listing 19.1. Of course, you don't have to configure your system identicallythe only goal is to make the Happy Axis page happy! Listing 19.1 Output from the Happy Axis Page [View full width] Examining webapp configuration Needed Components Found SAAJ API (javax.xml.soap.SOAPMessage) at C:\dev\tomcat\webapps\axis\WEB-INF\lib\ saaj.jar Found JAX-RPC API (javax.xml.rpc.Service) at C:\dev\tomcat\webapps\axis\WEB-INF\lib\ jaxrpc.jar Found Apache-Axis (org.apache.axis.transport.http.AxisServlet) at C:\dev\tomcat\webapps\ axis\WEB-INF\lib\axis.jar Found Jakarta-Commons Discovery (org.apache.commons.discovery.Resource) at C:\dev\tomcat\ webapps\axis\WEB-INF\lib\commons-discovery.jar Found Jakarta-Commons Logging (org.apache.commons.logging.Log) at C:\dev\tomcat\webapps\ axis\WEB-INF\lib\commons-logging.jar Found IBM's WSDL4Java (com.ibm.wsdl.factory.WSDLFactoryImpl) at C:\dev\tomcat\webapps\ axis\WEB-INF\lib\wsdl4j.jar Found JAXP implementation (javax.xml.parsers.SAXParserFactory) at C:\dev\tomcat\common\ endorsed\xmlParserAPIs.jar Found Activation API (javax.activation.DataHandler) at C:\dev\tomcat\common\lib\ activation.jar Optional Components Found Mail API (javax.mail.internet.MimeMessage) at C:\dev\tomcat\common\lib\mail.jar Warning: could not find class org.apache.xml.security.Init from file xmlsec.jar XML Security is not supported See http://xml.apache.org/security/ The core axis libraries are present. 1 optional axis library is missing Note: On Tomcat 4.x, you may need to put libraries that contain java.* or javax.* packages into CATALINA_HOME/commons/lib Note: Even if everything this page probes for is present, there is no guarantee your web service will work, because there are many configuration options that we do not check for. These tests are necessary but not sufficient Examining Application Server Servlet version 2.3 XML Parser org.apache.xerces.jaxp.SAXParserImpl Examining System Properties java.runtime.name=Java(TM) 2 Runtime Environment, Standard Edition sun.boot.library.path=C:\jdk1.3.1_01\jre\bin java.vm.version=1.3.1_01 java.vm.vendor=Sun Microsystems Inc. java.vendor.url=http://java.sun.com/ path.separator=; java.vm.name=Java HotSpot(TM) Client VM file.encoding.pkg=sun.io java.vm.specification.name=Java Virtual Machine Specification user.dir=C:\dev\tomcat\bin java.runtime.version=1.3.1_01 java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment java.endorsed.dirs=c:\dev\tomcat\bin;c:\dev\tomcat\common\endorsed os.arch=x86 java.io.tmpdir=c:\dev\tomcat\temp line.separator= java.vm.specification.vendor=Sun Microsystems Inc. java.awt.fonts= java.naming.factory.url.pkgs=org.apache.naming os.name=Windows 2000 java.library.path=C:\jdk1.3.1_01\bin;.;C:\WINDOWS\System32;C:\WINDOWS;c:\mysql\bin;C:\ jdk1.3.1_01\bin;c:\dev\ant\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;c:\ emacs-20.7\bin;c:\dev\cygwin\bin;c:\dev\maven\bin java.specification.name=Java Platform API Specification java.class.version=47.0 os.version=5.1 user.home=C:\Documents and Settings\a user.timezone=America/New_York catalina.useNaming=true java.awt.printerjob=sun.awt.windows.WPrinterJob file.encoding=Cp1252 java.specification.version=1.3 catalina.home=c:\dev\tomcat user.name=a java.class.path=C:\jdk1.3.1_01\lib\tools.jar;c:\dev\tomcat\bin\bootstrap.jar java.naming.factory.initial=org.apache.naming.java.javaURLContextFactory java.vm.specification.version=1.0 java.home=C:\jdk1.3.1_01\jre java.specification.vendor=Sun Microsystems Inc. user.language=en awt.toolkit=sun.awt.windows.WToolkit java.vm.info=mixed mode java.version=1.3.1_01 java.ext.dirs=C:\jdk1.3.1_01\jre\lib\ext sun.boot.class.path=C:\jdk1.3.1_01\jre\lib\rt.jar;C:\jdk1.3.1_01\jre\lib\i18n.jar;C:\jdk1. 3.1_01\jre\lib\sunrsasign.jar;C:\jdk1.3.1_01\jre\classes java.vendor=Sun Microsystems Inc. catalina.base=c:\dev\tomcat file.separator=\ java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport.cgi sun.cpu.endian=little sun.io.unicode.encoding=UnicodeLittle user.region=US sun.cpu.isalist=pentium i486 i386 Platform: Apache Tomcat/4.1.12 Don't worry if your output is different than in the preceding listing. Many of the properties should be different because file locations on your machine will likely be different than mine. By default, the basic installation has all the library files you need for the sample application, so you should be ready to proceed. You might have noticed in this listing that one of the optional libraries ( xmlsec.jar ) wasn't found. That's fineit isn't required for the sample application. After your Happy Axis page is happy, your Web Services server is configured correctly and you're ready to move on to the next step : configuring and building the client application. Configuring Axis in the Build EnvironmentNow that you've validated the basic Axis installation, it's time to build and deploy the sample Struts application. This isn't complex, but it takes a number of steps. All these steps are described in the following sections. Copy the Axis Web Application into the Build EnvironmentThe first step in preparing the build environment is to copy the axis Web application you just validated into the sample application build directory (this has actually already been done for you in the strutsWS application). This step is required because a part of the sample application includes the business logic that lives in the Axis server. After all, there's not much use invoking a Web Service unless it actually does something! To accomplish this, copy the axis Web application into the sample application main directory (into the directory strutsWS/axis ). Configure the Build Parameters in the build.properties FileA build script ( build.xml ) has been provided for use with the Jakarta Ant build tool to build the application. The build.xml file is located in the top directory of the sample application. In addition, a build.properties file is also provided that has two properties that must be configured for your environment:
After these two properties are set for your system, save the file and exit. Your environment is now set. The next steps have to do with using Axis to define and build a Web Service application. The Web Services Client: Struts and Axis IntegratedHere's a review of the overall application and how you're going to integrate Axis code with Struts:
Note that this design is virtually identical to the design in the previous chapter. Because of the design modularity, all that had to be done to change the application from using an EJB server to using a Web Service for back-end processing was to replace the facade class. Details of the facade class are provided later in the chapter after the code needed to interact with the Web Service has been explained. Java2WSDL and WSDL2Java: Automating Axis Code DevelopmentThe Axis developers provide two invaluable utilities to help you build Web Services applications: Java2WSDL and WSDL2Java . You'll see how valuable they are in the next few sections as we go through the steps to build the sample application. WSDL stands for Web Services Description Language and is used to provide a program language-independent description of a Web Service. WSDL is written using XML and provides information about the data types, operations, and access locations that make up the Web Service. This chapter doesn't provide detailed information about WSDSLyou can refer to one of the many good books available on SOAP and Web Services for that. However, the chapter presents the WSDL for the service you're building and provides an overview of it that should get you started. At a high level, the process that you follow for building your Web Service code is
Now let's dive in and start building the application! Step 1: Generate the WSDL for the ApplicationNote It's important to note that some steps in development of this sample application involve generating code automatically using the Java2WSDL and WSDL2Java utilities that are a part of Axis. These utilities are destructive in that every time they run, they overwrite all files they generate. For this reason, you should be very careful each time you run them that any files you want to keep are backed up in a safe location! In this step, you use the Axis Java2WSDL utility to generate WSDL for the application. To do so, you must first create some Java for it to use to generate the WSDL. The Java you must create consists of two classes. The first is the value object you're using. Java2WSDL determines the properties of the value object and generates the WSDL to allow it to be transferred using the Web Service. Remember that the Web Service server you're communicating with need not be written in Java. The WSDL generated here must carry all the information about the data being sent so that program at the other end can understand how to deserialize it. The second class you must create is a simple, Java interface file. This interface file (similar to the remote interface for an EJB) defines the business methods that the Web Service exposes. CustomerWSValueObject.java is the value object form bean used in this sample application. It's virtually identical to the value object from the previous chapter except for the class name and package. Its source code is shown in Listing 19.2. Listing 19.2 The CustomerWSValueObject.java Source Listingpackage struts.ch19.customer; /** * Example Value Object for showing integration of Struts with Web Services * * @author Kevin Bedell & James Turner * @version 1.0 */ public class CustomerWSValueObject { public CustomerWSValueObject () { this.name = ""; this.custId = ""; this.address = ""; this.city = ""; this.state = ""; this.zip = ""; } public CustomerWSValueObject (String name, String custId, String address, String city, String state, String zip ) { this.name = name; this.custId = custId; this.address = address; this.city = city; this.state = state; this.zip = zip; } private String name; public String getName() { return this.name; } public void setName(String nameus) { this.name = name; } private String custId; public String getCustId() { return this.custId; } public void setCustId(String custId) { this.custId = custId; } private String address; public String getAddress() { return this.address; } public void setAddress(String address) { this.address = address; } private String city; public String getCity() { return this.city; } public void setCity(String city) { this.city = city; } private String state; public String getState() { return this.state; } public void setState(String state) { this.state = state; } private String zip; public String getZip() { return this.zip; } public void setZip(String zip) { this.zip = zip; } } As you can see, this is a basic value object for use in storing information about a customer. The Java interface used in the sample application exposes only a single business method that's used to process address changes. The CustomerWS.java class is the Java interface used with this application to assist in generating the Web Service communications files. Its source code is shown in Listing 19.3. Listing 19.3 The CustomerWS.java Source Listingpackage struts.ch19.customer; import struts.ch19.customer.CustomerWSValueObject; /** * Interface describing a web service to process customer information changes **/ public interface CustomerWS { public CustomerWSValueObject addressChange( CustomerWSValueObject cvo ); } As you can see in the listing, the addressChange method accepts and returns a CustomerWSValueObject . This is how the Web Service is defined to allow passing data a bean at a time instead of a property at a time. After you run Java2WSDL next, you'll be able to see how this is defined in the WSDL. To simplify the running of the Java2WSDL utility, Listing 19.4 provides a batch program that configures the environment and runs Java2WSDL. For details about all the options for Java2WSDL and WSDL2Java , see the Reference Guide included in the Axis documentation. Note Prior to running java2WSDL , the two Java files used here must be compiled. To simplify this, a command file compile.cmd has been provided. This file is provided as a convenience and isn't discussed further. Any method of compiling these files will do as long as
Listing 19.4 The StrutsCh19Step1.cmd File for Generating WSDL [View full width] @ECHO OFF set JAVA_HOME=C:\jdk1.3.1_01 set JAVAC=%JAVA_HOME%\bin\javac.exe set JAVA=%JAVA_HOME%\bin\java.exe set AXIS_LIBS=.\axis\WEB-INF\lib set XERCES=.\lib\xerces.jar set AXIS_SRC=.\src REM Build out the classpath set LCP=.;%JAVA_HOME%\lib\tools.jar REM Add Axis jars to the classpath set LCP=%LCP%;%AXIS_LIBS%\axis.jar set LCP=%LCP%;%AXIS_LIBS%\axis-ant.jar set LCP=%LCP%;%AXIS_LIBS%\commons-discovery.jar set LCP=%LCP%;%AXIS_LIBS%\commons-logging.jar set LCP=%LCP%;%AXIS_LIBS%\jaxrpc.jar set LCP=%LCP%;%AXIS_LIBS%\log4j-1.2.4.jar set LCP=%LCP%;%AXIS_LIBS%\saaj.jar set LCP=%LCP%;%AXIS_LIBS%\wsdl4j.jar REM Xerces Parser set LCP=%LCP%;%XERCES% REM Add the app class files to the classpath set LCP=%LCP%;%AXIS_SRC% %JAVA% -cp %LCP% org.apache.axis.wsdl.Java2WSDL -o %AXIS_SRC%\struts\ch19\customer\ customer.wsdl -l"http://localhost:8080/axis/services/Customer" -n "urn:Customer" -p"struts.ch19.customer" "urn:Customer" struts.ch19.customer.CustomerWS Note This file is located in the strutsWS main directory (along with all the other command files for this chapter!). To run it, simply change to that directory and type its name. Prior to running this file, you must edit it and modify the JAVA_HOME parameter to point to the JDK you're using. Running this file batch file generates a WSDL file named customer.wsdl in the strutsWS/src/struts/ch19/customer directory. This file is shown in Listing 19.5. Listing 19.5 The customer.wsdl WSDL File [View full width] <?xml version="1.0" encoding="UTF-8"?> <wsdl:definitions targetNamespace="urn:Customer" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:apachesoap="http://xml.apache.org/xml-soap" xmlns:impl="urn:Customer" xmlns: intf="urn:Customer" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns: wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/ soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <wsdl:types> <schema targetNamespace="urn:Customer" xmlns="http://www.w3.org/2001/XMLSchema"> <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/> <complexType name="CustomerWSValueObject"> <sequence> <element name="address" nillable="true" type="xsd:string"/> <element name="name" nillable="true" type="xsd:string"/> <element name="zip" nillable="true" type="xsd:string"/> <element name="custId" nillable="true" type="xsd:string"/> <element name="state" nillable="true" type="xsd:string"/> <element name="city" nillable="true" type="xsd:string"/> </sequence> </complexType> <element name="CustomerWSValueObject" nillable="true" type="impl: CustomerWSValueObject"/> </schema> </wsdl:types> <wsdl:message name="addressChangeResponse"> <wsdl:part name="addressChangeReturn" type="intf:CustomerWSValueObject"/> </wsdl:message> <wsdl:message name="addressChangeRequest"> <wsdl:part name="in0" type="intf:CustomerWSValueObject"/> </wsdl:message> <wsdl:portType name="CustomerWS"> <wsdl:operation name="addressChange" parameterOrder="in0"> <wsdl:input message="intf:addressChangeRequest" name="addressChangeRequest"/> <wsdl:output message="intf:addressChangeResponse" name="addressChangeResponse"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="CustomerSoapBinding" type="intf:CustomerWS"> <wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="addressChange"> <wsdlsoap:operation soapAction=""/> <wsdl:input name="addressChangeRequest"> <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:Customer" use="encoded"/> </wsdl:input> <wsdl:output name="addressChangeResponse"> <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:Customer" use="encoded"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="CustomerWSService"> <wsdl:port binding="intf:CustomerSoapBinding" name="Customer"> <wsdlsoap:address location="http://localhost:8080/axis/services/Customer"/> </wsdl:port> </wsdl:service> </wsdl:definitions> If you're not used to reading WSDL, don't worry about it for now. Two important parts are highlighted in bold . These pieces are repeated in XML fragments below as they're explained. Here's the first XML fragment, which defines the value object: <complexType name="CustomerWSValueObject"> <sequence> <element name="address" nillable="true" type="xsd:string"/> <element name="name" nillable="true" type="xsd:string"/> <element name="zip" nillable="true" type="xsd:string"/> <element name="custId" nillable="true" type="xsd:string"/> <element name="state" nillable="true" type="xsd:string"/> <element name="city" nillable="true" type="xsd:string"/> </sequence> </complexType> As you can see, the WSDL created provides a language-independent description of the CustomerWSValueObject . Any other Web Services client could read this and understand how to deserialize the data, which is the key to Web Services interoperability, after all. The next fragment to review is where the WSDL defines the operations that the Web Service supports: <wsdl:message name="addressChangeResponse"> <wsdl:part name="addressChangeReturn" type="intf:CustomerWSValueObject"/> </wsdl:message> <wsdl:message name="addressChangeRequest"> <wsdl:part name="in0" type="intf:CustomerWSValueObject"/> </wsdl:message> <wsdl:portType name="CustomerWS"> <wsdl:operation name="addressChange" parameterOrder="in0"> <wsdl:input message="intf:addressChangeRequest" name="addressChangeRequest"/> <wsdl:output message="intf:addressChangeResponse" name="addressChangeResponse"/> </wsdl:operation> </wsdl:portType> This fragment identifies two types of elements: Two message elements and a single portType element. Although it might seem out of order, let's discuss the portType element first. A portType defines a collection of operation elements. The portType defined here is named CustomerWS after the CustomerWS.java file that Java2WSDL was run against. Within the portType element are defined one or more operation elements that specify the operations that the Web Service supports. In this case, there's only a single operation, named addressChange . It also specifies that addressChange takes a single input parameter, in0 . You'll see in a minute that in0 is a CustomerWSValueObject . The message elements in this fragment provide definitions that are needed by the operation element that we just discussed. You can look at the fragment to see how this works: The message elements are defined and then the operation elements refer to them. In this case, two message elements define the input and output (or request and response) required by the addressChange operation. If you look at the part elements enclosed by each of the message elements, you'll see that the input and return parameters (named in0 and addressChangeReturn , respectively) are both of type CustomerWSValueObject . To be honest, this is really all you need to know about WSDL for now. You can read the rest of the WSDL if you want, but it's not required to move forward with the sample application. The main thing to take away from this is to remember that WSDL provides an XML description of the Web Service that's language and platform independent. Step 2: Generate the Web Service Client CodeThe next step is to create the Java class files that are eventually incorporated into Struts to allow it to communicate with the Web Service server. Fortunately, Axis provides another utility to help with this. The Axis WSDL2Java utility performs a transformation opposite to the one performed in the previous step 1. WSDL2Java takes the WSDL that was just created and uses it to generate a series of Java class files that can be used to communicate with the Web Service that the WSDL describes. (In addition to generating Java class files for use in communicating to a Web Service server, WSDL2Java can also generate the Java class files needed by the server, but that's skipping ahead to step 3!) Another batch program is provided to assist you in running step 2. Listing 19.6 presents StrutsCh19Step2.cmd , a batch file that configures the environment that executes WSDL2Java to generate the code that runs in the Web Service client. For details about all the options for Java2WSDL and WSDL2Java , see the Reference Guide included in the Axis documentation. Listing 19.6 StrutsCh19Step2.cmd Creates Java Classes for Accessing the Web Service Server [View full width] @ECHO OFF set JAVA_HOME=C:\jdk1.3.1_01 set JAVAC=%JAVA_HOME%\bin\javac.exe set JAVA=%JAVA_HOME%\bin\java.exe set AXIS_LIBS=.\axis\WEB-INF\lib set XERCES=.\lib\xerces.jar set AXIS_SRC=.\src REM Build out the classpath set LCP=.;%JAVA_HOME%\lib\tools.jar REM Add Axis jars to the classpath set LCP=%LCP%;%AXIS_LIBS%\axis.jar set LCP=%LCP%;%AXIS_LIBS%\axis-ant.jar set LCP=%LCP%;%AXIS_LIBS%\commons-discovery.jar set LCP=%LCP%;%AXIS_LIBS%\commons-logging.jar set LCP=%LCP%;%AXIS_LIBS%\jaxrpc.jar set LCP=%LCP%;%AXIS_LIBS%\log4j-1.2.4.jar set LCP=%LCP%;%AXIS_LIBS%\saaj.jar set LCP=%LCP%;%AXIS_LIBS%\wsdl4j.jar REM Xerces Parser set LCP=%LCP%;%XERCES% REM Add the app class files to the classpath set LCP=%LCP%;%AXIS_SRC% %JAVA% -cp %LCP% org.apache.axis.wsdl.WSDL2Java -o %AXIS_SRC% -p struts.ch19.customer. client customer.wsdl Note This file is located in the strutsWS main directory (along with all the other command files for this chapter!). To run it, simply change to that directory and type its name. Prior to running this file, you must edit it and modify the JAVA_HOME parameter to point to the JDK you're using. Running this file causes a number of Java source files to be created. Notice that the last command in the file specifies a package ( struts.ch19.customer.client ) that's different from the one in the initial CustomerWS.java and CustomerWSValueObject.java files (in case you don't remember, that was struts.ch19.customer ). This is to make it easy to keep the files required for the Web Service client separate from all the other files. It also makes your code more organized and easier to understand. Following is a list of these files with a short description of what each is used for:
This step in the process was just to generate these files. Actually using them comes later, in step 5, when you integrate them into the sample Struts application. Step 3: Generate the Web Service Server CodeNow that you've created the Web Service client code, it's time to generate the corresponding code to run on the Web Service server. Why do you need to do this? Why can't the server just know how to accept all the data that you send it? The answer is really in two parts. The first part of the answer goes back to the earlier discussion about serialization/deserialization and knowing what data types to expect. This is one of the fundamental issues with Web Services: The code running on the server has to know what kinds of arguments to accept and how to deserialize them into meaningful data again after it receives them. So, the first reason to generate the server code is to handle communication of the data. The second reason is because the Web Service server code must provide you with a place to tie in to so that you can create a Web Service that actually does something. It also must connect the code where you tie in your business logic back to the portType s and operation s that the Web Service exposes to the client code. Now that you have a basic idea of what must be done, here's how to do it. Listing 19.7 presents StrutsCh19Step3.cmd , a batch file that configures the environment that executes the WSDL2Java to generate the code that runs on your Web Service server. For details about all the options for Java2WSDL and WSDL2Java , see the Reference Guide included in the Axis documentation. Listing 19.7 StrutsCh19Step3.cmd Creates Java Classes for Use by the Web Service Server [View full width] @ECHO OFF set JAVA_HOME=C:\jdk1.3.1_01 set JAVAC=%JAVA_HOME%\bin\javac.exe set JAVA=%JAVA_HOME%\bin\java.exe set AXIS_LIBS=.\axis\WEB-INF\lib set XERCES=.\lib\xerces.jar set AXIS_SRC=.\src REM Build out the classpath set LCP=.;%JAVA_HOME%\lib\tools.jar REM Add Axis jars to the classpath set LCP=%LCP%;%AXIS_LIBS%\axis.jar set LCP=%LCP%;%AXIS_LIBS%\axis-ant.jar set LCP=%LCP%;%AXIS_LIBS%\commons-discovery.jar set LCP=%LCP%;%AXIS_LIBS%\commons-logging.jar set LCP=%LCP%;%AXIS_LIBS%\jaxrpc.jar set LCP=%LCP%;%AXIS_LIBS%\log4j-1.2.4.jar set LCP=%LCP%;%AXIS_LIBS%\saaj.jar set LCP=%LCP%;%AXIS_LIBS%\wsdl4j.jar REM Xerces Parser set LCP=%LCP%;%XERCES% REM Add the app class files to the classpath set LCP=%LCP%;%AXIS_SRC% %JAVA% -cp %LCP% org.apache.axis.wsdl.WSDL2Java -o %AXIS_SRC% -s -S true -p struts.ch19. customer.server customer.wsdl Note This file is located in the strutsWS main directory (along with all the other command files for this chapter). To run it, simply change to that directory and type its name. Prior to running this file, you must edit it and modify the JAVA_HOME parameter to point to the JDK you're using. Notice that the package (denoted by the -p option) is struts.ch19.customer.server . This makes it easier to isolate the files generated for the server. Looking at the generated files, you can see that most of them are duplicates of the files that were generated for the client. Some of the files aren't used by the server, and some are. But it's best to deploy all the files with the server code just to be sure. The following are the four new files:
This step in the process was just to generate these files. Step 4: Compile, Deploy, and Test the Web Service Server CodeNow that you've generated all the files, the next step is to compile everything and make sure that you can deploy the files into your Axis server and get them to run correctly. Step 4 doesn't involve incorporating your Struts code or any code in the server that implements business logic. To begin, first make sure that you can deploy and access the Web Service. After that's done, you can move on to integrating your Struts code in Step 5. To accomplish this build process, a build.xml Ant script has been provided. Assuming that you edited the build.properties file earlier in the chapter, you should be able to go ahead and just run the build process now. To run the build, simply type ant deploy . You should see Ant compiling all the files and deploying two .war files strutsWS.war and axis.war into your Tomcat webapps directory. The output from the build process is presented in Listing 19.8. Listing 19.8 Output from a Successful Ant Build ProcessC:\dev\apps\strutsWS>ant deploy Buildfile: build.xml prepare: [echo] Tomcat Home = c:/dev/tomcat [echo] webapps Home = c:\dev\tomcat\webapps compile: [javac] Compiling 13 source files to C:\dev\apps\strutsWS\object [copy] Copying 13 files to C:\dev\apps\strutsWS\src build: [copy] Copying 13 files to C:\dev\apps\strutsWS\build\WEB-INF\classes [copy] Copying 8 files to C:\dev\apps\strutsWS\build\WEB-INF\lib [jar] Building jar: C:\dev\apps\strutsWS\deploy\strutsWS.war [copy] Copying 20 files to C:\dev\apps\strutsWS\axis\WEB-INF\classes [jar] Building jar: C:\dev\apps\strutsWS\deploy\axis.war deploy: [copy] Copying 1 file to C:\dev\tomcat\webapps [copy] Copying 1 file to C:\dev\tomcat\webapps BUILD SUCCESSFUL Total time: 27 seconds Note The build.xml file is located in the strutsWS main directory (along with all the other command files for this chapter). To run it, simply change to that directory and type ant deploy . Prior to running this file, you must edit the build.properties file and update it with the paths for your Tomcat home directory and your Tomcat webapps directory. The output of the build process is two .war files ( axis.war and strutsWS.war ) that are copied into your Tomcat webapps directory. Note that after the build process is complete, you might need to restart Tomcat to get the new .war files to deploy and run. In fact, some users have had to stop Tomcat, delete the existing axis and strutsWS subdirectories in their Tomcat webapps directory, and then restart to get the updated .war files to deploy. After the build is complete, Tomcat is running, and the .war files have been successfully deployed in Tomcat, you're ready for the last major piece in step 4: deploying the files into the Axis server. Deploying the Web Service is the process of registering it with the Web Service server and providing the details of how it should be accessed and how to process the data associated with it. When you ran WSDL2Java the last time, a deployment descriptor was generated and named deploy.wsdd . This file, which is located in the strutsWS/src/struts/ch19/customer/server directory, is presented in Listing 19.9. Listing 19.9 deploy.wsdd The Web Service Deployment Descriptor for the CustomerWS Web Service [View full width] !-- Use this file to deploy some handlers/chains and services --> <!-- Two ways to do this: --> <!-- java org.apache.axis.client.AdminClient deploy.wsdd --> <!-- after the axis server is running --> <!-- or --> <!-- java org.apache.axis.utils.Admin clientserver deploy.wsdd --> <!-- from the same directory that the Axis engine runs --> <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <!-- Services from CustomerWSService WSDL service --> <service name="Customer" provider="java:RPC"> <parameter name="wsdlTargetNamespace" value="urn:Customer"/> <parameter name="wsdlServiceElement" value="CustomerWSService"/> <parameter name="wsdlServicePort" value="Customer"/> <parameter name="className" value="struts.ch19.customer.server. CustomerSoapBindingSkeleton"/> <parameter name="wsdlPortType" value="CustomerWS"/> <parameter name="allowedMethods" value="*"/> <typeMapping xmlns:ns="urn:Customer" qname="ns:CustomerWSValueObject" type="java:struts.ch19.customer.server.CustomerWSValueObject" serializer="org.apache.axis.encoding.ser.BeanSerializerFactory" deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" /> </service> </deployment> As you can see, the Web Service deployment descriptor provides the Web Service server with the details for how to expose the Web Service, including how it's to be addressed by the client and how the data is to be serialized and deserialized. Note the serializer and deserializer attributes to the typeMapping element. These attributes reference the BeanSerializerFactory and BeanDeserializerFactory classes. These are classes that are Axis-specific and make it easy to send simple JavaBeans as parameters across an Axis-based Web Service. This support for serializing/deserializing JavaBeans is built into Axis and is one of the reasons for its popularity. They might not be recognizable by other Web service implementations, however, so they should be used with caution. To perform the deployment, a command file is provided. This file is named deployServer.cmd and it's shown in Listing 19.10. Listing 19.10 deployServer.cmd A Command File to Deploy the CustomerWS Web Service@ECHO OFF set JAVA_HOME=C:\jdk1.3.1_01 set JAVAC=%JAVA_HOME%\bin\javac.exe set JAVA=%JAVA_HOME%\bin\java.exe set AXIS_LIBS=.\axis\WEB-INF\lib set XERCES=.\lib\xerces.jar set AXIS_SRC=.\src REM Build out the classpath set LCP=.;%JAVA_HOME%\lib\tools.jar REM Add Axis jars to the classpath set LCP=%LCP%;%AXIS_LIBS%\axis.jar set LCP=%LCP%;%AXIS_LIBS%\axis-ant.jar set LCP=%LCP%;%AXIS_LIBS%\commons-discovery.jar set LCP=%LCP%;%AXIS_LIBS%\commons-logging.jar set LCP=%LCP%;%AXIS_LIBS%\jaxrpc.jar set LCP=%LCP%;%AXIS_LIBS%\log4j-1.2.4.jar set LCP=%LCP%;%AXIS_LIBS%\saaj.jar set LCP=%LCP%;%AXIS_LIBS%\wsdl4j.jar REM Xerces Parser set LCP=%LCP%;%XERCES% REM Add the app class files to the classpath set LCP=%LCP%;%AXIS_SRC% set ADMIN=org.apache.axis.client.AdminClient %JAVA% -classpath %LCP% %ADMIN% %AXIS_SRC%\struts\ch19\customer\server\deploy.wsdd Note The deployServer.cmd file is located in the strutsWS main directory (along with all the other command files for this chapter). To run it, simply change to that directory and type deployServer.cmd . Prior to running this file, you must make sure that Tomcat is running and that the axis and strutsWS.war files have been properly deployed. You'll know the deployment went correctly if you see the results: C:\dev\apps\strutsWS>deployserver - Processing file .\src\struts\ch19\customer\server\deploy.wsdd - <Admin>Done processing</Admin> After the Web Service is deployed, you should verify that the Web Service server shows it to be available. With Axis, this is a simple task. Just point your browser to http://localhost:8080/axis/index.html This is the same main page you used earlier to access the Happy Axis! page. This time, choose the option to "View the list of deployed Web services." In it, you should see your deployed Customer service. This means the deployment worked correctly. Clicking on the "wsdl" link brings back the WSDL for the Web Service. After you've validated that the Customer Web Service is correctly deployed to Axis, you're ready to move to the final step! Note If you don't see the service deployed, make sure that the axis.war file that the build process generated is loaded correctly. You might have to stop Tomcat, manually remove the axis directory from Tomcat's webapps directory, and then restart Tomcat. Step 5: Integrating with Struts and Building the Business Logic on the ServerIf the steps so far seemed complicated, they're really not that bad. After you get into development and go through this cycle a few times, the process goes much smoother. Almost everything you've done so far is the one-time setup and learning that enables you to get up and running. So, now that you've deployed and tested your Web Service server code, it's time for the last step: Actually integrating the code into your Struts application. There are two parts to performing the integration: integrating the Axis code with your Struts classes for the Web Service client code and adding business logic to the Web Services server classes. Let's start by integrating the Web Services code with the Struts code. The design of the Struts sample application makes it easy to complete the integration. Here's a quick overview:
This design is identical to the design used to communicate with the JBoss EJB server in the previous chapter. In fact, virtually the only changes (other than changing the Java package names and a few of the actual Java class file names) are modifications to the facade class. Everything else is identical. Speaking of changing Java package names, all the Struts code is in the package struts.ch19.customer.struts . This is parallel to the Axis client code in struts.ch19.customer.client and the Axis server code in struts.ch19.customer.server . Before jumping into the facade class, it's good to review the Action class so that you can understand how the facade is used. Listing 19.11 presents the file CustomerAction.java file. Listing 19.11 CustomerAction.java The Action Class for the Sample Struts/Axis Applicationpackage struts.ch19.customer.struts; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import struts.ch19.customer.struts.CustomerValueObject; /** * Action class to demonstrate EJB Integration with Struts * * @author Kevin Bedell & James Turner * @version 1.0 */ public class CustomerAction extends Action { /** * Update the customer's address. * * @param mapping The ActionMapping from this struts-config.xml entry * @param actionForm The ActionForm to process, if any * @param request The JSP request object * @param response The JSP response object * * @exception Exception if business logic throws an exception */ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { /* * Cast the form bean to CustomerForm */ CustomerForm cf = (CustomerForm) form; /* * Instantiate the facade - it hides the details * for us and simplifies the Action class. */ CustomerWSFacade facade = new CustomerWSFacade(); /* * The Value Object allows us to pass data "a bean at a time". * This increases speed, reduces maintenance and simplifies * tha Action class as well. Add methods to the form bean to * allow it to work with Value Objects too. */ CustomerValueObject cvo = cf.getValueObject(); try { /* * Pass in a value object, and get one in return */ cvo = facade.addressChange( cvo ); } catch (CustomerUpdateException cue) { // In a real application, this exception would be handled better. // return (mapping.findForward("CustUpdateError")); cue.printStackTrace(); } /* * Update the form bean with all values returned at once. */ cf.setValueObject(cvo); return (mapping.findForward("default")); } } The critical things here to note are the use of the facade class and the value object. This approach simplifies the design of the Action class a great deal. Also notice that the value object used in the Action class is the Axis-independent version. It's identical to the original value object we used with Java2WSDL to create the original WSDL way back in step 1. This was done to make sure that the value object was independent of Axis in case we want to switch to a new architecture later. (Don't worry, you'll see the WSDL2Java -generated value object later when we review the facade class.) Now that you've reviewed the Action class, it's time to review the facade class and understand exactly how the Struts/Axis integration works. The facade class, CustomerWSFacade.java , is presented in Listing 19.12. Listing 19.12 CustomerWSFacade.java The Facade That Actually Performs the Struts/Axis Integrationpackage struts.ch19.customer.struts; import java.util.Properties; import struts.ch19.customer.client.CustomerWS; import struts.ch19.customer.client.CustomerWSServiceLocator; import struts.ch19.customer.client.CustomerWSValueObject; /** * Example facade which hides interactions with the back-end web service * * @author Kevin Bedell & James Turner * @version 1.0 */ public class CustomerWSFacade { public CustomerWSFacade () { } /** * This method sends the value object to the Web Service * for it to process. It's goal is to hide the implementation * details of interacting with the Web Service.<p> * * Note that this method captures any Web Service related Exceptions * and rethrows them as "application-specific" Exception * (CustomerUpdateException). This helps to "decouple" the * application from the web service backend. * * @param cvo_in * @return CustomerValueObject * @throws CustomerUpdateException */ public CustomerValueObject addressChange( CustomerValueObject cvo_in ) throws CustomerUpdateException { try { // Use the web service locator created by WSDL2Java CustomerWSServiceLocator servLoc = new CustomerWSServiceLocator(); CustomerWS custService = servLoc.getCustomer(); // Create a WS value object - WS val obj is created by WSDL2Java CustomerWSValueObject wsCustVO = new CustomerWSValueObject(); wsCustVO.setName(cvo_in.getName()); wsCustVO.setCustId(cvo_in.getCustId()); wsCustVO.setAddress(cvo_in.getAddress()); wsCustVO.setCity(cvo_in.getCity()); wsCustVO.setState(cvo_in.getState()); wsCustVO.setZip(cvo_in.getZip()); wsCustVO = custService.addressChange(wsCustVO); cvo_in.setName(wsCustVO.getName()); cvo_in.setCustId(wsCustVO.getCustId()); cvo_in.setAddress(wsCustVO.getAddress()); cvo_in.setCity(wsCustVO.getCity()); cvo_in.setState(wsCustVO.getState()); cvo_in.setZip(wsCustVO.getZip()); return cvo_in; } catch( Exception e ){ throw new CustomerUpdateException(e.toString()); } } } To summarize how the facade works, it follows this path:
The fact that two kinds of value objects are used warrants a bit further explanation. The two types of value objects are struts.ch19.customer.struts.CustomerValueObject and struts.ch19.customer.client.CustomerWSValueObject . These two objects are actually very similar, but there's a good reason for having two. The reason for using two similar value objects is that doing so allows the Struts application to be completely isolated from the Axis code. This ensures that as the Axis code is modified or completely removed, little or no maintenance is required in the Action class or anywhere else in the Struts application. This is borne out by looking at the changes to the application from Chapter 18 to now. Because the application uses a facade class and a value object that's independent of the back-end system, the only thing that had to be done when the application changed from using JBoss to using Axis as its back-end server was to change the facade. Even the value object didn't have to changeit's still the same as it was previously. The final thing we must do to finish the integration between Struts and the Axis Web Service is to tie in the business logic on the Web Service server. This is straightforward as well. When the Axis WSDL2Java utility generated the Web Service server code back in step 3, it created an empty class for you to put your business logic in. The class's name is CustomerSoapBindingImpl.java . To demonstrate this, we'll use a before-and-after approach. First you'll be shown the file just as the WSDL2Java utility created it, and then you'll be shown the same file with the business logic code inserted. Listing 19.13 contains the CustomerSoapBindingImpl.java file just as it was generated by the WSDL2Java utility. Listing 19.13 CustomerSoapBindingImpl.java as It Was Created by WSDL2Java [View full width] /** * CustomerSoapBindingImpl.java * * This file was auto-generated from WSDL * by the Apache Axis WSDL2Java emitter. */ package struts.ch19.customer.server; public class CustomerSoapBindingImpl implements struts.ch19.customer.server.CustomerWS{ public struts.ch19.customer.server.CustomerWSValueObject addressChange(struts.ch19.customer.server.CustomerWSValueObject in0) throws java.rmi. RemoteException { return null; } } As you can see, there's not much there. It's just a stub class that returns a null . Listing 19.14 presents the same file after the business logic for this application has been added. Listing 19.14 CustomerSoapBindingImpl.java with Business Logic Added [View full width] /** * CustomerSoapBindingImpl.java * * This file was auto-generated from WSDL * by the Apache Axis WSDL2Java emitter. * * Following being automatically created by the Apache Axis WSDL2Java emitter, * it was modified with the actual web service business logic required. */ package struts.ch19.customer.server; import java.util.Random; public class CustomerSoapBindingImpl implements struts.ch19.customer.server.CustomerWS { public struts.ch19.customer.server.CustomerWSValueObject addressChange(struts.ch19. customer.server.CustomerWSValueObject in0) throws java.rmi.RemoteException { Random r = new Random(); switch (r.nextInt(3)) { case 0: in0.setAddress("Downing Street"); break; case 1: in0.setAddress("Via Del Museo De Prado"); break; case 2: in0.setAddress("The Outback Road"); break; } /* * This allows watching activity in the Tomcat window (if one exists) */ System.out.println( "CustomerSoapBindingImpl.addressChange() called." + " Set address to: " + in0.getAddress() ); return in0; } } You can see that some simple logic was added to Listing 19.14 just to send back a random address and print a message to the standard output when the method was called. In practice, you'd more likely build the business logic in some other class and have this class simply parse the input parameters and then call other classes that would perform the actual work. After you have modified this class with the preceding lines, re-run the Ant build process to deploy the new code to Tomcat. You might have to restart Tomcat again before testing the application. After this final step is complete, you should be able to point your browser to the address http://localhost:8080/strutsWS/Customer.do to test the final application. Note If you run into problems at this point, it may be useful to try the following:
|