Back to Thoughts On Technology Page | Back to Peter's Home Page | Back to Family Home Page



Simple Java Web Service Example
Using JWS

Peter Rose - 09/15/10


Abstract
Summary
Quick Metro tool use summary
Download and install necessary resources
Create new web services directory structure
Getting initial server files created
Generate server artifacts with wsgen
Adding a bean to web service reply
Getting initial client files created
Generate client artifacts with wsimport
Back To Server To Test Servlet
First web service call from Carl to Sam
Getting a client web service call through a proxy server



Top


Abstract
Using the tool Metro, create a local web services Tomcat server environment using JWS (Java Web Service) for the SOAP functionality wrapper as well as create a separate Tomcat client side environment application to test the web services connectivity.

In another article I discussed web services using Axis. Axis is an Apache Open Source project http://ws.apache.org/axis/java/user-guide.html and has a neat command line tool WSDL2Java that enables you to produce a .wsdl file and create all of the client side Java objects needed for the web service.

This article will use JWS technology if you need to stay under the Sun umbrella. It, too, has a tool similar to WSDL2Java, though the process is not quite as straight forward or easy to implement as Axis. My heartfelt thanks to my colleague David Sanborn who preceded me in this work by about a month, and when I was hopelessly lost in the weeds over some arcane issue shined a flashlight to point the way.

Top


Summary
Tools and envionment
Sun's web service server is Glassfish (https://glassfish.dev.java.net/). Glassfish is an open source application server project led by Sun Microsystems for the Java EE platform. Use this if you don't have an app server as it bundles all of the J2EE stuff along with web services.

For our purposes here, I want to use Apache Tomcat for our web server and not get into the overhead of Glassfish. Glassfish contains a standalone product called Metro, a high-performance, extensible, easy-to-use web service stack.

Within Metro are tools to generate needed artifacts. The two that I will discuss are: wsgen on the server side which reads a service endpoint implementation class and generates all of the portable artifacts for a JAX-WS web service, and wsimport on the client side which reads a WSDL and generates all the required artifacts for web service development, deployment, and invocation.

General Process
I will use these tools in command line format to build my web service classes rather than ant. Quite frankly, I'm not all that good with ant, and if you're not either, then you'd have to learn ant just to be able to understand the obvious.

You can save yourself reading all of this by simply looking at the Metro documentation on these tools. Sorry, but whoever wrote that stuff was not talking to someone like me; I found it about as helpful as trying to run with a small stone in my shoe: I could run, but man did it hurt. Having said that, the link will take you to the command line syntax that I'll be discussing - and if what I'm discussing gets out of date (as of 09/2010), then it will give the current form.

As an additional note, I use wsgen and wsimport to produce .java source classes that are put directly into the class path structure of my project. I do not bundle this stuff up as a .war/.jar that then needs to be deployed into Tomcat.

After doing a little housekeeping to get the environment setup, we'll create a pojo on the server side that we will later decide to make a web service out of, and then we'll show how to write a client object that access that object.

First, I'll show how to make the object available to be called, and then I'll show how to write a client object that can call it. What all this means is this object is going to be exposed as a web service which someone else, i.e. the client, on another server on another planet, will write a client object to access it.

Phases of Work

  • Download and install necessary files and resources
  • Create new web services directory structure
  • Getting initial files created
  • Generating web service wsdl file with JWS wsgen
  • Generate server side and client side classes for web service


Top


Quick Metro tool use summary

I am writing this after completing this entire guide to summarize the absolute key issues in getting Metro up and running. When I started this, I had an app written using Axis that had to be converted to Metro but I found the available Metro documentation so sparse that I ended up messing the whole application up trying to get the learning curve up to spead on Metro. So, I started from scratch and built two seperate Tomcat apps with one acting as the web service (jws with Sam Server) and the other as the client (jwsclient with Carl Client).

Though it took a long time to do this, I wrote it for someone with little experience in such things. However, if you just want the meat of it, then this section is all you need. It only took me 60 hours of research and developing the two simple little servlet apps to figure all of this out (with the full discussion of it consisting of one long document of about 60 screen pages as I just can not stand people who feel a need to break html documents up into short little paragraphs so they can clutter the issue with pages and pages and pages of ads from the local beauty shop or auto repair business to pay them money).

Download and install necessary resources
Just follow the instructions in the section Download and install necessary resources, sub-section: 'Use This Approach Instead - Using Brute Force Approach To Install' for both the server and client instances.

Server Side Development

  • Make the Metro entries into your web.xml
  • Create the sun-jaxws.xml file in WEB-INF
  • Build a web service annotated class
  • Run Metro wsgen tool to create .wsdl files and server web service classes
  • Make correct entry in service .wsdl for the 'soap:address location=' tag

Server Side Metro wsgen tool command line

wsgen
    -cp D:\zproj\dev\jws\jbuild  com.pmr.jws.ws.metro.dao.TellMeWSDAO
    -r D:\zproj\dev\jws\src\com\pmr\jws\ws\
    -s D:\zproj\dev\jws\src
    -servicename "{urn:dao.metro.ws.jws.pmr.com}TellMeWSDAOService"
    -portname "{urn:dao.metro.ws.jws.pmr.com}TellMeWSDAOPort"
    -wsdl -verbose -Xnocompile

Note that the first -cp option points to the root of the directory that contains the com.xxx path structure to your already compiled code and the second -cp option is the package path in that compiled code directory structure to the compiled class that you annotated with web service tags, i.e. this is the class that you want to provide as a web service to the outside world and it will be this class that is used to create the .wsdl etc. artifacts you will give to the client. The -r option give the tool the path to where you want those .wsdl artifiacts put. The -s option will use the path you gave as the second option to the -cp tag (i.e. com.pmr.jws.ws.metro.dao) to create a new jaxws directory that will contain all of the server side webservice enabled classes (as .java source files) you will need.

And an additional note concerning the urn tag in the wsgen command that appears in both -servicename and -portname options (and which translate into the 'namespace' designator in the .wsdl files) of

'dao.metro.ws.jws.pmr.com'

Though this could simply be 'bob' or 'http://mysite.com/youme/here' the use of a reversed path to the web service annotated object seems to be fairly common. But why? Well, for the same reason we put classes in package structures to uniquely identify them vs. Bob.class and Bob.class, for example. You will find that Metro sometimes has a tough time unscrambling a .wsdl with same namespace designators.

Client Side Development

  • Get the 3 Metro wsgen server generated resource artifact files
  • Run Metro wsimport tool to create client side web service access classes
  • Build a class that uses the generated access classes to call the web service

Client Side Metro wsimport tool command line

wsimport
    -s D:\zproj\dev\jwsclient\src
    -p com.carl.work.ws.metro.sam.source
    D:\zproj\dev\jwsclient\src\com\carl\work\ws\metro\sam\TellMeWSDAOService.wsdl
    -B-XautoNameResolution -extension -Xnocompile -verbose

Note that the -s option identifies the root level just prior to your com.xxx path structure. This path structure to where you want to put those files, i.e. what the package name of the files will be, is provided in the -p option. And the third option simply tells you where to find the .wsdl and other resource files that were generated on the server side.

Okay! That's it for the 'Quick' summary. If you got it, that's great. If you need more direction, then please feel free to read on with the following extremely sleep inducing discussion....

Top


Download and install necessary resources
After going to the Metro - https://metro.dev.java.net/ website and downloading the jar file, just unjar it (or use WinZip utility) into some directory out of your classpath.

You'll also need to find and download javax.jws-1.0 into your IDE's commonlib directory if you are not using jre 6.

Install Metro In Tomcat
Do NOT Use This Approach! - Using metro-on-tomcat.xml To Install

Though the following is given as installation instructions into tomcat, it doesn't do everything that you need to do, particularly if you are using an IDE, i.e. it doesn't tell you that you need to stuff these files into a common lib directory (note that javax.jws-1.0 is one of those files).

For Tomcat, the installation process copies the two Metro jar files into Tomcat's shared/lib directory. No Tomcat configuration files are modified.

The top-level directory of the Tomcat installation is passed as an argument to the ant command. To shutdown Tomcat, change directories, invoke ant, and restart Tomcat by executing these commands:

  • catalina.sh stop
  • ant -Dtomcat.home= -f /metro-on-tomcat.xml install
  • catalina.sh start

Use This Approach Instead! - Using Brute Force Approach To Install
Copy the following files to your Tomcat 'lib' directory:

  • webservices-rt.jar
  • webservices-tools.jar
  • webservices-extra.jar
  • webservices-extra-api.jar

Copy the following files to your Tomcat 'endorsed' directory:

  • jsr173_api.jar
  • webservices-api.jar

Install Metro In JDK
Copy the following files to your JDK 'lib/endorsed' directory:

  • webservices-api.jar

Copy the following files to your JDK 'jre/lib/endorsed' directory:

  • webservices-api.jar

Change web.xml
Add the following lines to 'web.xml' in your server side application's web.xml (you could do this in tomcat's web.xml but that clouds the issue of how you do this in your own application and makes upgrading Tomcat just that much more difficult and prone to errors):

<!-- Metro Web Service Listener -->
<listener>
  <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener>/listener-class<
</listener>

<!-- Metro Web Service Servlet-->
<servlet>
  <servlet-name>MetroWS</servlet-name>
  <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
  <load-on-startup<1>/load-on-startup<
</servlet>

<!-- Metro Web Service Servlet Mappings-->
<!-- Note: URLs take the form http://SERVERNAME/PROJECTNAME/metro-services/web serviceNAME -->
<servlet-mapping<
  <servlet-name>MetroWS</servlet-name>
  <url-pattern>/metro-services/*</url-pattern>
</servlet-mapping>

Update Your IDE
Copy the following files to your IDE 'commonjars' directory, and make sure to update your IDE library references:

  • webservices-rt.jar
  • webservices-tools.jar
  • webservices-extra.jar
  • webservices-extra-api.jar


Top


Create new web services directory structure
There are two directory structures we're going to be talking about: that on the server side and that on the client side.

Server Side - D:\zproj\dev\jws\src\com\pmr\jws
Client Side - D:\zproj\dev\jwsclient\src\com\carl\work

You'll note that I'm using my own machine/structure to put these files, but just assume each directory is off in cyberspace somewhere else as each will have its own tomcat instance running. Under each \jws and \work are the usual suspects: controller, beans, bo, dao, etc. to hold the project's files. Note that only jwsclient will need a war directory with a jsp. We'll use this jsp to initiate a call to the server project's web service and to display the result. Smooth....

Top


Getting initial server files created
We're going to start our development process with the server side configuration and development for Sam Server. If you don't have the server side set up, then you have nothing the client can call, duh....

Sam Server builds a service class (a DAO or Data Access Object) on his site (the server site) under a new directory jws/dao called AppTellMeDAO.java that he wants to expose as a web service.

D:\zproj\dev\jws\src\com\pmr\jws\dao\AppTellMeDAO.java

AppTellMeDAO.java


package com.pmr.jws.dao;

import java.util.Date;

public class AppTellMeDAO {
  public String tellMeTheDate() {
    return "Hey! It's now: " + new Date().toString();
  }

  public String whoAmI(String thisIsMe) {
    return thisIsMe;
  }
}

AppTellMeDAO.java is just a business object on Samís site; nothing special other than to note that it's mixed in with all of Sam's other application code.

This is important to note because this file itself will not be the file that is exposed as a web service. Why? Well, as you'll see, Metro configures its web service enabled objects and graphs differently than Axis (I wrote a guide to setting up web services under Axis if you're interested; probably not...). If you configure this object as a web service enabled object, then your entire application becomes tied up with Metro. Try unscrambling that if you shift to Axis or Uncle Billy's web services And Real Estate Software Tools...

The example is simple, and may in fact, contain dozens of methods. However, Bob wants to expose just the tellMeTheDate() method on this object to a web service call. The method returns a simple String. You have to be careful what you try to return in a web service, i.e. watch out for complex structures or very language specific returns like a java.util.List which is specific to java (rather use an array of your objects: MyObject[]). These things are listed out, so, check on what constitutes good returnable data types.

Because Sam only wants to expose the tellMeTheDate() method as a Metro web service, he would create a new object under a new directory structure he creates just for web service Metro files:

D:\zproj\dev\jws\src\com\pmr\jws\ws\metro\dao

Someday, if Sam wants to create an Axis web service, he can create a \pmr\jws\ws\axis\dao structure:

D:\zproj\dev\jws\src\com\pmr\jws\ws\axis\dao

TellMeWSDAO.java


package com.pmr.jws.ws.metro.dao;

import java.util.Date;
import javax.jws.WebMethod;
import javax.jws.web service;
import com.pmr.jws.dao.AppTellMeDAO;

@web service()
public class TellMeWSDAO {
    @WebMethod()
    public String tellMeTheDate() {
        AppTellMeDAO dao = new AppTellMeDAO();
        return dao.tellMeTheDate();
    }

    @WebMethod(exclude=true)
    public String whoAmI(String thisIsMe) {
        return thisIsMe;
    }
}

Note that the method annotations to TellMeWSDAO.java are different depending on if you want to expose the method to a web service call or not. Methods that will be exposed to a web service call don't need parameters (i.e. @WebMethod()), whereas any methods that will not be exposed must be identified with 'exclude=true' annotation (i.e. @WebMethod(exclude=true)).

Also note the instantiation of and call to the AppTellMeDAO which separates metro web services from the actual application.

And, as a general aggrivated developer comment: who wrote the api for this that you have to specifically identify methods not to be exposed? Why isn't the default condition just to not expose methods that lack an @WebMethod() annotation? Silly. The process Axis uses makes sooooo much more sense from an application design standpoint. Oh well....

Top


Generate server artifacts with wsgen
The 'stuff' that you will need for Sam's server side web service files are the .wsdl and other config/schema files as well as the generated .java web service artifact files (which, in turn, once created will be be compiled into .class files by your IDE).

Add jars to classpath
First, in order to use any of the tools we'll be discussing, Sam has to have the following in his path and java classpath (along with jdk and tomcat references):

set PATH=%PATH%;D:\devtools\metro\bin
set CLASSPATH=%CLASSPATH%;D:\devtools\metro\lib

If you don't have this, then run it now so you have the Metro references. In addition, be sure you run a build against your code base so that class files are generated where you will be creating your .wsdl file from or they will not be picked up correctly.

To make things easier, I have a setenv.bat file in my ant directory that I run to set all this stuff up. If you know how to do this, then this is what I use:

set ANT_HOME=D:\devtools\apache-ant-1.7.1
set CATALINA_HOME=C:\zdev\apache-tomcat-6.0.20_WS
set JAVA_HOME=D:\devtools\Java\jdk1.6.0_17
set CATALINA_BASE=C:\zdev\apache-tomcat-6.0.20_WS
set PATH=%PATH%;D:\devtools\apache-ant-1.7.1\bin
set PATH=%PATH%;C:\zdev\metro\bin
set CLASSPATH=%CLASSPATH%;C:\zdev\metro\lib

Generate .wsdl and server class files
Specifically note here that Metro provides two ways to generate this .wsdl file: apt if all you have is the .java files, or wsgen when you have compiled classes with annotations. Using apt is pretty complex to use so my advice (and this example) is to use wsgen with compiled classes with annotations.

Okay, cd into your (looking at things from Bobís perspective here) projectís classes directory. Note that it is server side Bob that is doing this: it is he that needs to provide a .wsdl to a client and to generate the server side web service class files. That .wsdl file will be used by the client to generate all of the files necessary for the client to find this TellMe file.

Actually, I should be saying those .wsdl files because wsgen produces two of them: a Service and a Port .wsdl. And just be aware that when you identify these files in the following command line that you always name them the same as your annotated web service class, i.e. TellMeWSDAOService and TellMeWSDAOPort.

The following command line argument discussion is for the most part right from the Metro site:

  • -cp: Specifies the location of the service implementation class
  • -d: Specify directory where to place generated output files. Use the -Xnocompile option as your IDE or build scripts should take care of compiling these classes - which will also make sure all of your envirnonment is set up to work with Metro
  • -r: Used only in conjunction with the -wsdl option. Specify where to place generated resource files such as WSDLs
  • -s: Specify where to place generated source files
  • -servicename: Used only in conjunction with the -wsdl option. Used to specify a particular wsdl:service name to be generated in the WSDL. Example, -servicename "{http://mynamespace/}MyService"
  • -portname: Used only in conjunction with the -wsdl option. Used to specify a particular wsdl:port name to be generated in the WSDL. Example, -portname "{http://mynamespace/}MyPort"
  • -wsdl: By default wsgen does not generate a WSDL file. This flag is optional and will cause wsgen to generate a WSDL file and is usually only used so that the developer can look at the WSDL before the endpoint is deploy. The protocol is optional and is used to specify what protocol should be used in the wsdl:binding. Valid protocols include: soap1.1 and Xsoap1.2. The default is soap1.1. Xsoap1.2 is not standard and can only be used in conjunction with the -extension option.
  • -verbose: Print version information. Use of this option will ONLY print version information. Normal processing will not occur.
  • -Xnocompile: Used in place of the -d option. If you simply don't list -d, then class files will be produced anyway in the same directory the batch file is run from.

The jwsserver.bat file in your ant directory will look like this (note same start as setenv.bat):

set ANT_HOME=D:\devtools\apache-ant-1.7.1
set CATALINA_HOME=C:\zdev\apache-tomcat-6.0.20_WS
set JAVA_HOME=D:\devtools\Java\jdk1.6.0_17
set CATALINA_BASE=C:\zdev\apache-tomcat-6.0.20_WS
set PATH=%PATH%;D:\devtools\apache-ant-1.7.1\bin
set PATH=%PATH%;C:\zdev\metro\bin
set CLASSPATH=%CLASSPATH%;C:\zdev\metro\lib

wsgen
    -cp D:\zproj\dev\jws\jbuild  com.pmr.jws.ws.metro.dao.TellMeWSDAO
    -r D:\zproj\dev\jws\src\com\pmr\jws\ws\
    -s D:\zproj\dev\jws\src
    -servicename "{urn:dao.metro.ws.jws.pmr.com}TellMeWSDAOService"
    -portname "{urn:dao.metro.ws.jws.pmr.com}TellMeWSDAOPort"
    -wsdl -verbose -Xnocompile

Note that the first -cp option points to the root of the directory that contains the com.xxx path structure to your already compiled code and the second -cp option is the package path in that compiled code directory structure to the compiled class that you annotated with web service tags, i.e. this is the class that you want to provide as a web service to the outside world and it will be this class that is used to create the .wsdl etc. artifacts you will give to the client. The -r option give the tool the path to where you want those .wsdl artifiacts put. The -s option will use the path you gave as the second option to the -cp tag (i.e. com.pmr.jws.ws.metro.dao) to create a new jaxws directory that will contain all of the server side webservice enabled classes (as .java source files) you will need.

Running this bat file will generate the wrapper classes needed for TellMeWSDAO annotated with @web service annotation inside a jaxws directory that wsgen creates under dao

D:\zproj\dev\jws\src\com\pmr\jws\ws\metro\dao\jaxws

The contents of this directory are the class files of the web method(s) you have exposed to a web service call:

  • TellMeTheDate.java
  • TellMeTheDateResponse.java

In addition, the following resource files are generated per the -r command under:

D:\zproj\dev\jws\src\com\pmr\jws\ws\

  • TellMeWSDAO.wsdl - the artifact from portname
  • TellMeWSDAO_schema1.xsd - schema file
  • TellMeWSDAOService.wsdl - the artifact from servicename

Note that although the servicename and portname parameters were named with 'Service' and 'Port', only the service .wsdl file name reflects this. Don't ask why....

Recapping server side development... so far
Okay, let's recap all of the server side construction that we've done. Sam Server set up his directory structure to accomodate creating a web service for the file TellMeWSDAO and how he set that up to act as a proxy to call the actual business object AppTellMeDAO. Here's a recap of that directory structure after running wsgen:

D:\zproj\dev\jws\src\com\pmr\jws\dao\AppTellMeDAO.java

D:\zproj\dev\jws\src\com\pmr\jws\ws\TellMeWSDAO.wsdl
D:\zproj\dev\jws\src\com\pmr\jws\ws\TellMeWSDAO_schema1.xsd
D:\zproj\dev\jws\src\com\pmr\jws\ws\TellMeWSDAOService.wsdl

D:\zproj\dev\jws\src\com\pmr\jws\ws\metro\dao\TellMeWSDAO.java
D:\zproj\dev\jws\src\com\pmr\jws\ws\metro\dao\jaxws\TellMeTheDate.java
D:\zproj\dev\jws\src\com\pmr\jws\ws\metro\dao\jaxws\TellMeTheDateResponse.java


But wait! If you order in the next ten minutes, I'll throw in the following section, Adding a bean to return in the web service reply, for free!

Top


Adding a bean to web service reply
And you thought we were done with Sam.... Nope. Sam's web service only returns a String reply; pretty trite stuff and not very real world useful. Adding a bean that is returned in the web service call is really not very dramatic, but it's nice to see just what the changes in the generated artifacts are when you do.

The first step is to make a copy of all of the files that you have generated so far so you can compare them later with the new files. This is particularly true of the .wsdl and .xsd files as the .wsdl is what Carl Client will be given to generate client side files that will be needed to access Sam's web service. So, it will be really interesting for you to see how the SOAP protocol deals with marshalling of objects via these files.

What we're going to do with Sam's web service is create a new method on the AppTellMeDAO.java that will return an Array of BubbaBean.java objects, i.e. BubbaBean[]. Sweet. Sam creates BubbaBean.java (note that it specifically implements Serializable) in the new com.pmr.jws.beans directory.

BubbaBean.java


package com.pmr.jws.beans;

public class BubbaBean implements Serializable {
    private static final long serialVersionUID = 1L;
    private String bubbasMessage = "";

    public String getBubbasMessage() {
        return bubbasMessage;
    }
    public void setBubbasMessage(String bubbasMessage) {
        this.bubbasMessage = bubbasMessage;
    }
}

Next, we have to create a new method on Sam's application business class AppTellMeDAO.java called getAllBubbasMessages() that returns a BubbaBean[] array.

New AppTellMeDAO.java


package com.pmr.jws.dao;

import java.util.Date;
import com.pmr.jws.beans.BubbaBean;

public class AppTellMeDAO {
    public String tellMeTheDate() {
        return "Hey! It's now: " + new Date().toString();
    }

    public BubbaBean[] getAllBubbasMessages() {
        int max = 3;
        BubbaBean[] beans = new BubbaBean[max];
        for(int i=0; i<max; i++) {
            BubbaBean bean = new BubbaBean();
            bean.setBubbasMessage("I'm happy BubbaBean number " + i);
            beans[i] = bean;
        }
        return beans;
    }
}

And now, Sam's web service class TellMeWSDAO.java needs to have a new method that will return that Array of BubbaBean objects to the caller:

New TellMeWSDAO.java


package com.pmr.jws.ws.metro.dao;

import javax.jws.WebMethod;
import javax.jws.web service;
import com.pmr.jws.beans.BubbaBean;
import com.pmr.jws.dao.AppTellMeDAO;

@web service()
public class TellMeWSDAO {
    @WebMethod()
    public String tellMeTheDate() {
        AppTellMeDAO dao = new AppTellMeDAO();
        return dao.tellMeTheDate();
    }

    @WebMethod()
    public BubbaBean[] getThoseMessages() {
        AppTellMeDAO dao = new AppTellMeDAO();
        return dao.getAllBubbasMessages();
    }

    @WebMethod(exclude=true)
    public String whoAmI(String thisIsMe) {
        return thisIsMe;
    }
}

Almost done. Now, just run the same wsgen command via the jwsserver.bat file and see what happens! It's a wonderful thing....

But there's more actually.... To fully establish Sam's web service, we're going to have to create a web.xml file and other artifacts and deploy a war file into Tomcat so that when Carl Client calls in, the message will get routed properly. The problem with learning this stuff is that if I show this now, you'll have no contex as to why we are doing the various steps on the server side. Instead, I'll first develop the calling code on Carl Client's site. That way, you'll see the error message that comes back and will better understand what needs to be done on the server side to make this all happen.

Top


Getting initial client files created
Carl Client is going to begin his work by setting up the following base directory structure:

D:\zproj\dev\jwsclient\src\com\carl\work
D:\zproj\dev\jwsclient\src\com\carl\work\controller\WorkAction.java
D:\zproj\dev\jwsclient\src\com\carl\work\bo\WorkBO.java
D:\zproj\dev\jwsclient\war\carl\work\work.jsp

Create Duplicate Tomcat Instance for client side
It will be really, really helpful if you know how to set Tomcat up, and build a servlet and .jsp file that can communicate with each other. If you don't, do not dispair! Read my Simple Java Servlet with JSP Example and follow the pattern there for getting the WorkAction.java and work.jsp to communicate with each other.

The tomcat references that we previously created were for the server side development and contain Metro specific references and files. We'll be developing code on the client side to call the server, so you'll need another instance of tomcat on the client side for that.

All you have to do is go to the directory where you put the original tomcat and create another directory called tomcat_wsc (as in _web serviceclient) - call it anything you want (I will be in my example code, i.e. apache-tomcat-6.0.20_WSC); it doesn't matter what you call it (like the old joke: What do you call a dog with no legs? You don't bother to call it anything because it won't come anyway... ba-da-bing).

Next, go into the client tomcat server.xml file and change all of the 8080 port references to something like 8090 and all of the 8443 references to something like 8493 and the 8005 server shutdown port to something like 8555. For example:

Client Tomcat server.xml changes


    <Connector port="8090" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8493" />

and

    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8099" protocol="AJP/1.3" redirectPort="8493" />

and

    <Server port="8555" shutdown="SHUTDOWN">

Just in case you're wondering why, because both Sam Server and Carl Client sites will be residing on the same development machine and sharing the same jdk environment, the 8090 port will be used to bring up Carl's work.jsp, but the web service call from the client to hit Sam's web service will be to Sam's port 8080, the standard default for tommycat. You need this 8090 port on Carl's client site because you have to have two tommycats running in your same jdk environment to test this stuff and they can't both be running on the same port. Smooth....

Create Carl's client side objects
We're going to build Carl's site iteratively, with the first step of creating the WorkAction.java class. Now, when I built this class (as well as work.jsp), I did it in small increments to test each step along the way. You'll also see that WorkBO.java has a very simple getWork() method that just returns a String[] to display on work.jsp. This is the state that you will find all of this code in at this stage in the development. In the next sections, I'll actually write a WorkBO.java method that will make a web service call over to Sam's tomcat instance to the method that just returns the single string. Then, we'll work on bring back the array of BubbaBean.java objects as the final step. Here we go!

WorkAction.java


package com.carl.work.controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.carl.work.bo.WorkBO;

public class WorkAction extends HttpServlet {
    public void doGet(
            HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("Entering WorkAction.doGet()");

        RequestDispatcher dispatcher = null;
        String action = (String) request.getParameter("action");
        System.out.println("  --action is: " + action);

        request.setAttribute("bubba", null);
        String forwardTo = "/work/work.jsp";

        if (action == null || action.trim().equals("")) {
            String displayMsg = "Fool! No 'action' parameter received!";
            request.setAttribute("message", displayMsg);
        } else if (("gimmethestuff").equalsIgnoreCase(action.trim())) {
            handleGimmeTheStuffRequest(request, response, forwardTo);
        } else {
            String displayMsg = "We have no idea what you are trying to do. Dial 911....";
            request.setAttribute("message", displayMsg);
        }

        if(request.getAttribute("bubba") != null) {
            String[] sbubba = (String[])request.getAttribute("bubba");
            int bubsize = sbubba.length;
            System.out.println("  --bubba size: "+bubsize);
        } else {
            System.out.println("  --Sorry, no Bubbas....");
        }
        returnMapping(request, response, forwardTo);
    }

    private void handleGimmeTheStuffRequest(
            HttpServletRequest request,
            HttpServletResponse response,
            String forwardTo) {
        String method = "handleGimmeTheStuffRequest";
        System.out.println("Entering WorkAction.handleGimmeTheStuffRequest()");
        WorkBO dao = new WorkBO();
        String[] beans = dao.doTheWork();
        request.setAttribute("bubba", beans);
        String displayMsg = "Here's the stuff!";
        request.setAttribute("message", displayMsg);
    }

    /**
     * Generic method for forwarding to next jsp. If an error occurs, it's just
     * swallowed and life goes on blindly.
     *
     * @author peter.rose
     * @return void
     */
    private void returnMapping(
            HttpServletRequest request,
            HttpServletResponse response,
            String forwardTo) {
        RequestDispatcher dispatcher =
            getServletContext().getRequestDispatcher(forwardTo);
        try {
            dispatcher.forward(request, response);
        } catch (ServletException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return;
    }
}


Next, build WorkBO.java, the object I mentioned in WorkAction.java that will actually be doing the web service call.

WorkBO.java


package com.carl.work.bo;

public class WorkBO {
    public String[] doTheWork() {
        String[] tmp = new String[2];
        tmp[0] = "I'm working. Don't bother me.";
        tmp[1] = "I'm still working. Don't bother me.";
        return tmp;
    }
}


Next, build work.jsp

work.jsp


<%@ page contentType="text/html; charset=iso-8859-1" language="java" %>
<html>
<head>
<title>Carls Client JSP</title>
</head>
<body>

<form method="GET">
<input type="hidden" name="action" value="gimmethestuff">

<table>
    <tr>
        <th colspan="9">Carls Client JSP</th>
    </tr>
    <tr>
        <td colspan="9" style="background-color:white;"> </td>
    </tr>
    <tr>
        <td colspan="9">
            <input type="submit" value="Make The Call!">
        </td>
    </tr>

    <%
        String msg = (String)request.getAttribute("message");
        if(msg != null) {
            %>
            <tr>
                <td colspan="9">
                    A special message for you - <%=msg%>
                </td>
            </tr>
    <% } else { %>
            <tr>
                <td colspan="9">
                    You gotta hit the 'Make The Call!' button to see anything, fool!
                </td>
            </tr>
    <% } %>

    <br><br><hr>
    <%
    //Determine if there is data to display
    String[] strbeans = (String[])request.getAttribute("bubba");
    if(strbeans != null) {
        %>
        <tr>
            <td colspan="9">
                I got stuff!
            </td>
        </tr>
        <%
        for(int i=0; i<strbeans.length; i++) {
            String strbean = strbeans[i];
            %>
            <tr>
                <td colspan="9">
                    Bubba says: <%=strbean%>
                </td>
            </tr>
        <% } %>
    <% } %>

    <tr>
        <td colspan="9" style="background-color:white;"> </td>
    </tr>
</table>
</form>

</body>
</html>


Create all client side resource and build files
The following url is what we will use to access Carl Client's site to pull up work.jsp. To support this, we need to build a bunch of housekeeping and utility files on Carl's client site to support this call:

http://localhost:8090/carl/ws/work.jsp and ultimately: http://localhost:8090/carl/ws to hit the servlet directly.

Remember that when you run your initial setenv.bat and then tomstart.bat, you can test the integrity of the Tomcat instance itself via http://localhost:8090/ to make sure your settings are correct.

Here is a list of the resource files we need to build not only for the client side but you'll also need to create versions of these files for Sam's server side deployment and operation:

  • setenv.bat: Sets up the environment for the build/deploy process in work.xml
  • custom.properties: Sets the projects build work.xml properties
  • context.xml: Identifies the context that routes a url
  • web.xml: Project's Tomcat directives
  • carl.xml: Project's ant build file
  • carlbuild.bat: Shortcut build command into work.xml

Please note that there may be references in these files that are specific to my own build environment (such as compilelib in work.xml) that you may get build errors for. Just make the changes as you see fit...

setenv.bat


set ANT_HOME=D:\devtools\apache-ant-1.7.1
set CATALINA_HOME=C:\zdev\apache-tomcat-6.0.20_WSC
set JAVA_HOME=D:\devtools\Java\jdk1.6.0_17
set CATALINA_BASE=C:\zdev\apache-tomcat-6.0.20_WSC
set PATH=%PATH%;D:\devtools\apache-ant-1.7.1\bin

custom.properties


tomcat.deploy.home=C:\\zdev\\apache-tomcat-6.0.20_WSC
tomcat.home=C:\\zdev\\apache-tomcat-6.0.20_WSC
dir.deploy=${tomcat.deploy.home}\\webapps
javaexec=D:\\devtools\\Java\\jdk1.6.0_17\\bin\\javac
javac.compiler=javac1.6

context.xml


This file is found in war/carl/META-INF

<Context path="/carl" docBase="carl" reloadable="true" userNameing="false" privileged="true" debug="0" swallowOutput="false"/>


web.xml


<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.
    //DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <display-name>Carls Work Application</display-name>
    <description>Carls Work Application</description>

    <servlet>
        <servlet-name>ws</servlet-name>
        <servlet-class>
            com.carl.work.controller.WorkAction
        </servlet-class>
    </servlet>


    <servlet-mapping>
        <servlet-name>ws</servlet-name>
        <url-pattern>/ws</url-pattern>
    </servlet-mapping>
</web-app>

carl.xml


<?xml version="1.0" encoding="UTF-8"?>

<project name="main" default="init">
    <property environment="env"/>
    <property file="custom.properties"/>
    <property name="projectname" value="carl"/>
    <property name="dir.root" value="../"/>
    <property name="build.compiler" value="${javac.compiler}"/>
    <property name="dir.lib" value="${dir.root}/compilelib"/>
    <property name="dir.src" value="${dir.root}/src"/>
    <!--property name="dir.utils.src" value="${dir.root}/../utils/src"/-->
    <property name="dir.archives" value="${dir.root}/archives"/>
    <property name="dir.build" value="${dir.root}/build"/>
    <property name="dir.deploy" value="${tomcat.home}/webapps"/>
    <property name="javac" value="${javaexec}"/>
    <property name="dir.warbase" value="${dir.root}/war"/>
    <property name="warfilename" value="${projectname}.war"/>
    <property name="warcontext" value="${projectname}"/>
    <property name="dir.jdk" value="${env.JAVA_HOME}/jre/lib"/>


    <target name="init">
        <echo message="${projectname}"/>
        <echo message="${build.compiler}"/>
        <echo message="${build.compiler.fulldepend}"/>
        <echo message="${compileWithDebug}"/>
        <echo message="${dir.root}"/>
        <echo message="${dir.src}"/>
        <echo message="${dir.utils.src}"/>
        <echo message="${dir.archives}"/>
        <echo message="${dir.build}"/>
        <echo message="${dir.lib}"/>
        <echo message="${dir.deploy}"/>
        <echo message="${dir.jdk}"/>
        <echo message="${javac}"/>
        <echo message="${dir.warbase}"/>
        <echo message="${warfilename}"/>
        <echo message="${warcontext}"/>
    </target>


    <target name="prepare" depends="init">
        <mkdir dir="${dir.archives}"/>
        <mkdir dir="${dir.build}"/>
    </target>


    <target name="reset">
        <antcall target="prepare"/>
    </target>


    <path id="project.classpath">
        <pathelement location="${dir.src}"/>
        <fileset dir="${tomcat.home}/lib">
            <include name="jsp-api.jar"/>
        </fileset>
        <fileset dir="${tomcat.home}/lib">
            <include name="servlet-api.jar"/>
        </fileset>
        <fileset dir="${dir.lib}">
            <include name="*.jar"/>
        </fileset>
    </path>


    <target name="build_carl">
        <delete file="${dir.archives}/${projectname}.jar"/>
        <javac srcdir="${dir.src}"
            includes="com/carl/work/**/*.*"
            destdir="${dir.build}"
            fork="yes"
            executable="${javaexec}"
            compiler="${build.compiler}"
            deprecation="off">
        <classpath refid="project.classpath"/>
        </javac>
        <antcall target="jar_carl"/>
    </target>


    <target name="build_war" depends="reset">
        <delete file="${dir.archives}/${warfilename}"/>
        <antcall target="build_carl"/>
        <copy todir="${dir.build}/jsp/${projectname}">
            <fileset dir="${dir.warbase}/${projectname}" includes="**/*.jsp"/>
        </copy>
        <!--
            TOUCH ALL THE JSPS
        -->
        <touch>
            <fileset dir="${dir.build}/jsp/${projectname}"/>
        </touch>
        <!--
            WAR CALL
        -->
        <war    destfile="${dir.archives}/${warfilename}"
                webxml="${dir.warbase}/${projectname}/WEB-INF/web.xml"
                basedir="${dir.warbase}/${projectname}"
                excludes="**/web.xml"
                includes="${projectname}/**/*.*,**/*.jsp,
                    css/*.css,images/*.*,WEB-INF/lib/*.*,WEB-INF/*.*,
                    WEB-INF/classes/resources/*.*,javascript/*.js,xslt/*.xslt,
                    WEB-INF/classes/**/*.class,META-INF/context.xml">
            <lib dir="${dir.lib}" includes="*.jar"/>
            <lib dir="${dir.archives}"
                includes="${projectname}.jar"/>
        </war>
        <!--
            COPY THE WAR TO ARCHIVES
        -->
        <copy todir="${dir.root}/../deploy/war">
            <fileset dir="${dir.archives}"
                includes="${warfilename}"/>
        </copy>
    </target>


    <target name="jar_carl">
        <jar jarfile="${dir.archives}/${projectname}.jar"
        basedir="${dir.build}"
        includes="com/carl/work/**/*.class"/>
    </target>


    <target name="deploy" depends="build_war">
        <unwar src="${dir.archives}/${warfilename}"
            dest="${dir.deploy}/${warcontext}"/>
    </target>


</project>

carlbuild.bat


ant -f carl.xml deploy

Try it!
Execute the following url to hit Carl's site from the browser:

http://localhost:8090/carl/ws

Then see what happens when the submit button 'Make The Call!' is selected. It's a wonderful thing...

Top


Generate client artifacts with wsimport
Now that we know Carl's site works, we can generate the necessary files that he will need in order to make a web service call over to Sam's site. Of particular note here is that we are going to use Metro on Carl's site. We could take the .wsdl files from Sam's site and run an Axis generator against them; that's supposed to be the beauty of these .wsdl files is that they are usable across all platforms that support web services.

Create a new client side object to hold returned data
We're going to create a new DAO object that will be specifically configured to make the web service call to Sam's site, retrieve the reply, and package it up into a useful, non-dependant object to send back around Carl's site.

What do I mean by 'non-dependant object'? Well, as you'll recall when we built Sam's site, his intent is to send over an array of BubbaBean objects, i.e. BubbaBean[]. What you'll see from the execution of the .wsdl file here on Carl's site using the wsimport tool is the creation of a BubbaBean, i.e. Carl will need a defination of what a BubbaBean is on his site because that's what will be coming over to him. Now, because this bean is going to be created, Carl could just pass it around his site, even the whole array of them could be sent off to the .jsp for display.

But what happens when Sam goes out of business, and Carl has to make a web service call out to Sally's web service and her .wsdl is mandating SallyBeans? For one, all of Carl's code that references a BubbaBean will now have to change to reference SallyBeans and their new properties. Not good.

So, the first thing Carl has to decide is what the data he is going to need from these web service calls will be and then he has to create his own bean for that, a CarlsBean, for example. Then, regardless of whose beans come in through a web service call (Sam's BubbaBeans or Sally's SallyBeans), Carl will strip out the data he needs and populate his own CarlsBean[] array and no other code on his site will need to change. Sound good? Okay, here we go.

We're going to create and put this new CarlsBean into the new directory:

D:\zproj\dev\jwsclient\src\com\carl\work\beans

CarlsBean.java


package com.carl.work.beans;

public class CarlsBean {
    private static final long serialVersionUID = 1L;
    private String value1;
    private String value2;

    public String getValue1() {
        return value1;
    }
    public void setValue1(String value1) {
        this.value1 = value1;
    }
    public String getValue2() {
        return value2;
    }
    public void setValue2(String value2) {
        this.value2 = value2;
    }
}

Create new client side object to call Sam's web service
The specific object that will be configured to call Sam's web service will be named WSValueSamDAO.java, and we're going to put it in it's own separate directory:

D:\zproj\dev\jwsclient\src\com\carl\work\ws\metro\sam\dao

The reason we create a separate ws\metro\sam\dao branch, for example, is in case at some point in the future we need a sally\dao that we want to unscramble using Axis. And, as when we developed Sams's web service and different objects and artifacts were created, when Carl runs Sam's .wsdl file with wsimport, there will be a BubbaBean that's going to need to be stored somewhere. That 'somewhere' is going to be under \ws\[metro | axis]\[sam | sally]\-somewhere-

WSValueSamDAO.java


package com.carl.work.ws.metro.sam.dao;

import com.carl.work.beans.CarlsBean;

public class WSValueSamDAO {
    private static final long serialVersionUID = 1L;

    public CarlsBean[] getValues() {
        CarlsBean[] beans = null;

        return beans;
    }
}

Note the non-descript method CarlsBean[] getValues(). If you ever have to create a WSValueSallyDAO.java, then that object would have a CarlsBean[] getValues(). You could thus build some sort of factory object that would return the appropriate type of bean you need and then call the method polymorphically from off somewhere. You just have to think a little ahead when you're building this stuff.

As you'll recall, Carl has a WorkBO.java that is acting as a service object. What? When we select the submit button on work.jsp, the request goes to controller.WorkAction.java which in turn instantiates Work.BO and calls the appropriate method on it such that it goes off to WSValueSamDAO to get the data (to return back up the stack to display on work.jsp). Ba-da-bing....

Now, I'm not teaching object oriented design here or how to code an app. You've got the bones of what needs to be done so you'll have to fill in the blanks as to how the .jsp, controller, and bo all work together to call the WSValueSamDAO. All I'm going to do at this point is show you the final code that you'll need, how to run wsimport, where to put the generated artifacts, and then what the guts of WSValueSamDAO is going to have to be to finish this discussion off. You'll see, for example, reference to a new WSException.java object. As I mentioned, you'll just have to 'fill in the blanks'. Good luck....

Changes and additions to all objects to support call
First, we add the following section into work.jsp:

work.jsp addition


    <%
    //Determine if CarlsBean data to display
    com.carl.work.beans.CarlsBean[] beans =
        (com.carl.work.beans.CarlsBean[])request.getAttribute("wsresults");
    if(beans != null) {
        %>
        <tr>
            <td colspan="9">
                I got stuff!
            </td>
        </tr>
        <%
        for(int i=0; i<beans.length; i++) {
            com.carl.work.beans.CarlsBean bean = beans[i];
            %>
            <tr>
                <td colspan="3">
                    <%=bean.getValue1()%> says:
                </td>
                <td colspan="6">
                    <%=bean.getValue2()%>
                </td>
            </tr>
        <% } %>
    <% } %>
}

Here's the addition to WorkAction.java that will recognize the new wsresults param from work.jsp:

WorkAction.java


        } else if (("wsresults").equalsIgnoreCase(action.trim())) {
            handleWSCallSam(request, response, forwardTo);

and the new method body:

    private void handleWSCallSam(
            HttpServletRequest request,
            HttpServletResponse response,
            String forwardTo) {
        System.out.println("  --handleWSCallSam()");
        String displayMsg = "";
        WorkBO bo = new WorkBO();
        WSValueSamDAO dao = new WSValueSamDAO();

        String theTimeStr = "- no time returned! -";
        CarlsBean[] beans = null;

        try {
            theTimeStr = dao.getTheTime();
            beans = bo.wsCallSam(dao);
            if(beans != null && beans.length > 0) {
                beans = null;
                displayMsg = "Here are the results from the "
                    + "webservice call to Sam's site:";
            } else {
                displayMsg = "Woops! No results found from "
                    + "the web service call to Sam's site.";
            }
        } catch (WSException e) {
            displayMsg = "Yo! There was a problem: "
                + e.getMessage();
        }

        displayMsg = "On web service date of " + theTimeStr
            + " we are told: " + displayMsg;
        request.setAttribute("wsresults", beans);
        request.setAttribute("message", displayMsg);
    }

Run wsimport to create connectivity and artifact objects to Sam's site
Like the Metro wsgen that created web service server artifacts for Sam's site, the Metro wsimport tool will generate web service client artifacts for Carl to use. Carl's going to have to run this to see what the guts of his WSValueSamDAO is going to have to look like.

Sam will have had to provide Carl with the following artifacts to do this

  • TellMeWSDAO.wsdl - the artifact from portname
  • TellMeWSDAO_schema1.xsd - schema file
  • TellMeWSDAOService.wsdl - the artifact from servicename

And they will be placed at: D:\zproj\dev\jwsclient\src\com\carl\work\ws\sam

So, here's the wsimport tags that we're going to use:

  • -s: Specify where to place generated source code files. keep is turned on with this option
  • -p: Specifying a target package via this command-line option, overrides any wsdl and schema binding customization for package name and the default package name algorithm defined in the specification
  • -B: Pass this option to JAXB schema compiler
  • -extension: allow vendor extensions (functionality not specified by the specification). Use of extensions may result in applications that are not portable or may not interoperate with other implementations
  • -Xnocompile: Do not compile generated Java files
  • -verbose: Output messages about what the compiler is doing
  • -wsdllocation: @WebServiceClient.wsdlLocation value

The jwsclient.bat file in your ant directory will look like this (note same start as setenv.bat and with the reference to the tommycat 20_WSC that we have running on Carl's site that uses port 8090):

set ANT_HOME=D:\devtools\apache-ant-1.7.1
set CATALINA_HOME=C:\zdev\apache-tomcat-6.0.20_WSC
set JAVA_HOME=D:\devtools\Java\jdk1.6.0_17
set CATALINA_BASE=C:\zdev\apache-tomcat-6.0.20_WSC
set PATH=%PATH%;D:\devtools\apache-ant-1.7.1\bin
set PATH=%PATH%;C:\zdev\metro\bin
set CLASSPATH=%CLASSPATH%;C:\zdev\metro\lib

wsimport
    -s D:\zproj\dev\jwsclient\src
    -p com.carl.work.ws.metro.sam.source
    D:\zproj\dev\jwsclient\src\com\carl\work\ws\metro\sam\TellMeWSDAOService.wsdl
    -B-XautoNameResolution -extension -Xnocompile -verbose

Note that the -s option identifies the root level just prior to your com.xxx path structure. This path structure to where you want to put those files, i.e. what the package name of the files will be, is provided in the -p option. And the third option simply tells you where to find the .wsdl and other resource files that were generated on the server side.

CAUTION: Before you run this .bat file, you will have to do all of the Metro jar file installation into Carl's tomcat, and jdk as you did for Sam. If Carl is only acting as a client, then you do not make the Metro entries into Carl's web.xml nor do you need the sun-jaxws.xml file. If you put this stuff in and you don't have an object annotated for providing web services, then tommycat is going to barf pretty badly at you.

Running this bat file will generate the source classes needed for Carl's application. Note that jwsclient.bat puts these into:

D:\zproj\dev\jwsclient\src\com\carl\work\ws\sam\source

The contents of this directory is:

  • BubbaBean.java
  • GetThoseMessages.java
  • GetThoseMessagesResponse.java
  • ObjectFactory.java
  • package-info.java
  • TellMeTheDate.java
  • TellMeTheDateResponse.java
  • TellMeWSDAO.java
  • TellMeWSDAOService.java


Top


Back To Server To Test Servlet
Okay, if you've got this far, just go back now to Sam's site, create some kind of welcome.jsp that just shoots an html message out to the browser, and then build similar resource stuff for Sam's site, i.e. jwsbuild.xml, jws.bat, contex.xml, edit the web.xml with the new welcome.jsp, etc.

Remember, what you did before on Sam Server's site was to only run the Metro wsgen tool to generate the web service artifacts; we never actually made sure everything was configured correctly so tommycat would properly display a welcome.jsp page. Not that we are concerned with developing an app on Sam's site; the web service call from Carl to Sam's site bypasses all that stuff and will hit the WorkBO.java directly.

But it would be nice to know that the site functions as advertised, right? Right. So, go do all that now, and I'll just wait here for you. But I can't wait toooo long....

Create Sam Server's servlet test files
We have just a few files to create or modify to test Sam's servlet, so here we go:

AppTellMeAction.java


package com.pmr.jws.controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AppTellMeAction extends HttpServlet {
    public void doGet(
            HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("Entering AppTellMeAction.doGet()");

        String displayMsg = "";
        String forwardTo = "/work/welcome.jsp";
        String action = (String) request.getParameter("action");
        if (action == null || action.trim().equals("")) {
            displayMsg = "Fool! No 'action' parameter received!";
            request.setAttribute("message", displayMsg);
        } else if (("hitme").equalsIgnoreCase(action.trim())) {
            displayMsg = "OUCH! I've been hit!";
            request.setAttribute("message", displayMsg);
        } else {
            displayMsg = "We have no idea what you are trying to do. Dial 911....";
            request.setAttribute("message", displayMsg);
        }

        System.out.println("  --Exiting after producing message: " + displayMsg);
        RequestDispatcher dispatcher =
            getServletContext().getRequestDispatcher(forwardTo);
        try {
            dispatcher.forward(request, response);
        } catch (ServletException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return;
    }
}

welcome.jsp - at war\jws\work


<%@ page contentType="text/html; charset=iso-8859-1" language="java" %>
<html>
<head>
<title>Sams Server JSP</title>
</head>
<body>

<form method="GET">
<input type="hidden" name="action" value="hitme">

<table>
    <tr>
        <th colspan="9">Sams Server JSP</th>
    </tr>
    <tr>
        <td colspan="9" style="background-color:white;"> </td>
    </tr>
    <tr>
        <td colspan="9">
            <input type="submit" value="Hit me one more time!">
        </td>
    </tr>

    <%
        String msg = (String)request.getAttribute("message");
        if(msg != null) {
            %>
            <tr>
                <td colspan="9">
                    A special message for you - <%=msg%>
                </td>
            </tr>
    <% } else { %>
            <tr>
                <td colspan="9">
                    You gotta hit me one more time to see anything, fool!
                </td>
            </tr>
    <% } %>


    <tr>
        <td colspan="9" style="background-color:white;"> </td>
    </tr>
</table>
</form>

</body>
</html>

You'll need to add the following two entries into Sam's web.xml to recognize the servlet path:

web.xml



    <servlet>
        <servlet-name>ws</servlet-name>
        <servlet-class>
            com.pmr.jws.controller.AppTellMeAction
        </servlet-class>
    </servlet>

and

    <servlet-mapping>
        <servlet-name>ws</servlet-name>
        <url-pattern>/ws</url-pattern>
    </servlet-mapping>

Fire it up and test the servlet
Are you done yet?

Now are you done?

I'm waiting....

Alright. You should be all set to fire up tommycat and see the welcome.jsp by hitting http://localhost:8080/jws/ws.

Ouch!That didn't work did it? Nope. You got the following tommycat barf, didn't you?

com.sun.xml.ws.transport.http.servlet.WSServletException: WSSERVLET11: failed to parse runtime descriptor: vax.xml.ws.web serviceException: Runtime descriptor "/WEB-INF/sun-jaxws.xml" is mising

There's a real got-cha here. Obviously, tommycat puked up a lot more than just that line, but that's the thrust of the issue: the file /WEB-INF/sun-jaxws.xml is missing. What the.... yeah.

Now, go on the internet and spend 20 minutes trying to figure out just what's going on. You will be very disturbed to learn that many IDEs that have plug ins to produce this stuff create this file for you and dump it into WEB-INF.

Great. How many stupid posts from people who use these IDEs on 'how to build a simple web service in 5 minutes using NetBeans' do you have to read before seeking psychiatric care if your company doesn't f'n use NetBeans? And not to beat this to death, but just wait until you have to work with .wsdl files from other web service products and try to get Metro to function as advertised; it's a nightmare. But, I digress....

Just drop the following into your WEB-INF.

sun-jaxws.xml


<?xml version="1.0" encoding="UTF-8"?>
<!-- Used by Metro Web Services -->
<!-- This can be tested via: http://localhost:8080/jws/webservices/tellme -->
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">
    <endpoint
        name="TellMeWSDAOService"
        implementation="com.pmr.jws.ws.metro.dao.TellMeWSDAO"
        url-pattern="/webservices/tellme"
    />
</endpoints>

The sun-jaxws.xml file acts similarly to web.xml. An 'endpoint' can be considered the tag that identifies the web service that you are exposing to the outside world. Thus, if Sam's servlet receives as the 'endpoint' of a url the token '/webservices/tellme' then the Metro engine (which is triggered by the /webservices/* token in web.xml) will route this call to the 'tellme' webservice of TellMeWSDAOService which implements com.pmr.jws.ws.metro.dao.TellMeWSDAO.java.

And just to be sure you understand the power and functionality of sun-jaxws.xml, let's say that Sam also wants to open up a different web service, the CollectEmployeeBirthdateService. After writing a CollectEmployeeBirthdateWSDAO and doing all the other plumbing around this, he would just add the following endpoint tag to his sun-jaxws.xml file:

Edited sun-jaxws.xml


    <endpoint
        name="CollectEmployeeBirthdateService"
        implementation="com.pmr.jws.ws.metro.dao.CollectEmployeeBirthdateWSDAO"
        url-pattern="/webservices/empbday"
    />

    And the url would become

    http://localhost:8080/jws/webservices/empbday

    to invoke that web service.

Final success!
Hit the server welcome.jsp via: http://localhost:8080/jws/ws and tommycat shows it's happy and running Metro as a service:

INFO: Metro monitoring rootname successfully set to: com.sun.metro:pp=/,type=WSEndpoint,name=/jws-TellMeWSDAOService-TellMeWSDAOPort
Sep 16, 2010 3:45:18 PM com.sun.xml.ws.transport.http.servlet.WSServletDelegate INFO: WSSERVLET14: JAX-WS servlet initializing
Entering AppTellMeAction.doGet()
--Exiting after producing message: Fool! No 'action' parameter received!
Entering AppTellMeAction.doGet()
--Exiting after producing message: OUCH! I've been hit!

Top


First web service call from Carl to Sam
So, we've got two servlets that can run in different Tomcat instances. Sam has opened his AppTellMeDAO.java object up for a web service call via the TellMeWSDAO.java annotated object which Carl's WSValueSamDAO.java object will call.

So, our next task here is to build Carl's WSValueSamDAO.java object, get both Tomcat instances running, and then, from Carl's site, use the following url to bring up Carl's page from where you will invoke Sam's web service:

http://localhost:8090/carl/ws

Build Carl's client object to call Sam's web service

WSValueSamDAO.java


package com.carl.work.ws.metro.sam.dao;

import javax.xml.ws.BindingProvider;

import com.carl.work.beans.CarlsBean;
import com.carl.work.bo.WSException;
import com.carl.work.ws.metro.sam.source.TellMeWSDAO;
import com.carl.work.ws.metro.sam.source.TellMeWSDAOService;

public class WSValueSamDAO {
    private static final long serialVersionUID = 1L;

    public CarlsBean[] getValues() throws WSException {
        System.out.println("Entering WSValueSamDAO.getValues()");
        CarlsBean[] beans = null;

        return beans;
    }


    public String getTheTime() throws WSException {
        System.out.println("Entering WSValueSamDAO.getTheTime()");
        String value1 = "bob";
        try {
            TellMeWSDAOService service = new TellMeWSDAOService();
            TellMeWSDAO port = service.getTellMeWSDAOPort();
            /*
             * Now, set the correct soap:address location tag port address
             * for the server environment in TellMeWSDAOService.wsdl
             */
            //(String)CommonUtils.getProperty("wsurl.tellme")
            ((BindingProvider)port).getRequestContext().put(
                    BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                    "http://localhost:8080/jws/webservices/tellme");
            value1 = port.tellMeTheDate();
            System.out.println("  --And the date is: = " + value1);
        } catch (Exception ex) {
            String errMsg = "Bat-ju-ju getting the time: = " + ex.getMessage();
            System.out.println(errMsg);
            throw new WSException(errMsg);
        }
        return value1;
    }
}

Of most interest in the above code is the assignment

binding the address dynamically to .wsdl


            ((BindingProvider)port).getRequestContext().put(
                    BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
                    "http://localhost:8080/jws/webservices/tellme");

This has to be done because if you look closely at the soap:address assignment line in TellMeWSDAOService.wsdl, you'll see that the default value is garbage:

Segment of TellMeWSDAOService.wsdl


  <service name="TellMeWSDAOService">
    <port name="TellMeWSDAOPort" binding="tns:TellMeWSDAOPortBinding">
      <soap:address location="REPLACE_WITH_ACTUAL_URL"/>
    </port>
  </service>

If you don't address this problem, then when you go to call the web service from your client, you'll get the following Exception: 'Unsupported endpoint address: REPLACE_WITH_ACTUAL_URL'. I had no clue when this happened and had to go seek enlightenment from my colleague David Sanborn. Thanks, once again, David....

Turns out that you need to plug into the .wsdl location tag the value: 'http://localhost:8080/jws/webservices/tellme'.

I know, I know.... You're wondering just where in the hell I pulled the soap:address location value from. Well, if you'll recall from our discussion of creating the Metro services in web.xml, that configuration indicates that you can test the Metro web service 'service' via '/webservices/*' where the '*' is some specific web service.

That '*' value are the various 'url-pattern=' tags you create in the sun-jaxws.xml, i.e. that's where you route calls to your web service annotated objects on the server. So, my entry in sun-jaxws.xml of /webservices/tellme directs that call to the com.pmr.jws.ws.metro.dao.TellMeWSDAO.java object.

So, that full url of http://localhost:8080/jws/webservices/tellme is what needs to go into the soap:address location tag in TellMeWSDAOService.wsdl before you hand that off to a client to build the objects he needs or his call will fail.

By the way, if you tell a client developer to enter the url 'http://localhost:8080/jws/webservices/tellme' on his (i.e. the client) side, he will get a page back that displays web services connectivity information about Sam's server. On that page will be a link for 'WSDL:'. If the client is looking for a .wsdl file to use and hits the link (or just types the following url into the browser to start with: http://localhost:8080/jws/webservices/tellme?wsdl), he'll get back in the browser TellMeWSDAOService.wsdl with the correct soap:address location tag put into that place! Magic! Just don't forget that for the client to build the needed objects to connect to a Metro web service the other two files need to be sent along as well.

And, not to beat this to death, but if you are in a commercial development environment having many servers for dev and test and prod environments, then you could put the various soap:address location url values into that envirnonemt's properties file and thus pass your code from environment to environment and always build the correct .wsdl references through the java object on the client. This is shown in the comment above the binding command:

//(String)CommonUtils.getProperty("wsurl.tellme")

Top


Getting a Client Web Service Call Through a Proxy Server
As developers, we are usually constrained behind fire walls and proxy servers that limit our ability to test the code that we write.

To test your web service client against a remote web service from your local machine, all you have to do is register your proxy server's properties in your System Properties object.

In current IE browser, for example, to find these settings, open browser Tools; Internet Options; Connections; Lan Settings; Advanced and copy the values. If the host names don't work, ping them and then substitute the ip address.

Generally, you'll want to set these properties in your application's startup servlet. But just remember not to check this code into your code repository or your security folks will get in a twist; it's just so you can run some local testing. Here's a method that shows how to register these properties:


    private void registerLocalProxySettingsForweb serviceTesting() {
        System.setProperty("http.proxyHost", "MyServersProxy");
        System.setProperty("http.proxyPort", "8080");
        System.setProperty("https.proxyHost", "MyServersSecureProxy");
        System.setProperty("https.proxyPort", "8443");
    }


Back to Thoughts On Technology Page | Back to Peter's Home Page | Back to Family Home Page