A Common Ant Build File for Metro-Based Services and Clients

by Harold Carr

Metro is a high performance, extensible, easy to use web service stack. It combines the JAX-WS reference implementation with Web Services Interoperability Technologies (WSIT), formerly known as Project Tango. WSIT includes features that enable advanced web service interoperability with the .NET Windows Communication Foundation (WCF), a set of technologies for building and running connected systems. Metro is bundled as part of the GlassFish Server, and runs in other containers such as Tomcat.

An earlier Tech Tip, Testing Interoperability Between Metro and .NET, described an interoperability test in which a Metro-based client communicates with a WCF-based service. In using Metro-based services for these tests, I sometimes start with a Web Service Definition Language (WSDL) file. Sometimes I build Metro-based services from Java classes. Sometimes I deploy the services to containers. Sometimes I don't deploy to a container. To simplify the task of using Metro-based services for these tests, I have a common ant build.xml file. The file handles all these combinations of web service sources and deployment approaches. This Tech Tip describes the contents and operation of the build.xml file.

A .tar file, CommonAntBuild.tar, accompanies this tip. The tar file contains the code and build files referenced in the tip. Note that the directory structure of the code uses symbolic links that work on UNIX and LINUX platforms. The tip assumes that you are running on a UNIX or LINUX platform. If you are running on a Windows platform, you will need to use other techniques for sharing common files -- these techniques are not covered in this tip.

Top-Level Targets

To get started, let's see what the build.xml file advertises by entering the following commands. Note that the commands in this tip assume that you have connected to the root directory of the source code associated with this tip.

   cd examples/from-java-container
   ant

In response, you should see the following build file targets:

   help:
        [echo] service-from-wsdl-container : Builds, wars, deploys service
        [echo] service-from-java-container : Builds, wars, deploys service
        [echo] service-from-wsdl-publish   : Builds, wars, publishes service
        [echo] service-from-java-publish   : Builds, wars, publishes service
        [echo] service-undeploy            : Undeploys the service WAR from the container
        [echo] client                      : Builds and runs the client
        [echo] clean                       : Delete all generated code

Each of the targets that begin with service-from- either builds a service from a WSDL description or from a Java class annotated with @WebService. It then either creates a WAR file and deploys the war file to a container or it invokes the Endpoint.publish() method to publish the web service endpoint using the HTTP server built into the Java platform.

General Project Layout

Here is the general layout of a project that uses the common build file.

   cd examples/from-java-container
   ls -alF
   -rw-r--r--  1 carr  staff   74 May 27 09:21 .README
   lrwxr-xr-x  1 carr  staff    5 May 27 09:00 build-common@ -> ../..
   -rw-r--r--  1 carr  staff  436 May 27 09:06 build.properties
   lrwxr-xr-x  1 carr  staff   29 May 27 09:00 build.xml@ -> build-common/build-common.xml
   drwxr-xr-x  4 carr  staff  136 May 27 09:07 etc/
   drwxr-xr-x  3 carr  staff  102 May 27 08:44 src/

To avoid duplicating files, I use symbolic links. The common build file is located in the root directory. It is named build-common.xml and linked in the current directory as build.xml. The following lines in the file import property files:

   <project basedir="." default="help" name="tango/code/build-common">

       <property file="build.properties"/>
       <property file="${tools.properties}"/>
       <property file="${container.properties}"/>

The first import is for the build.properties file in the current directory. The first two lines of the build.properties file define the other two property files to be imported. As shown below, the two other files to be imported are tools.properties and tomcat-container.properties:

   tools.properties=build-common/tools.properties
   container.properties=build-common/tomcat-container.properties

The tools.properties file defines settings for the wsgen and wsimport commands. The wsgen command generates Java API for XML Web Services (JAX-WS) portable artifacts for a web service. The wsimport command generates JAX-WS portable artifacts for a web service client. Here are the contents of the tools.properties file:

   # WSGEN options
   wsgen.verbose=true
   wsgen.keep=true

   # WSIMPORT options
   wsimport.debug=false
   wsimport.verbose=false
   wsimport.keep=true
   wsimport.extension=
   wsimport.xendorsed=true

You can either globally control settings for all the code that uses this property file or you can define a property file specifically for a particular project.

The tomcat-container.properties file sets up ant constants that are used to build and deploy a service in a Tomcat container. Here are the contents of the tomcat-container.properties file:

   catalina.home=/usr/local/hc/java/apache/apache-tomcat-5.5.26
   lib.home=${catalina.home}/shared/lib
   lib.endorsed=${catalina.home}/common/endorsed
   lib.glassfishv3=${catalina.home}
   deploy.dir=${catalina.home}/webapps

Note that if you use Tomcat with the common build file, you need to have Metro already installed into Tomcat.

There are two other properties files of interest in the root directory: glassfish-container.properties and no-container.properties. The glassfish-container.properties file sets up ant constants that are used to build and deploy a service in a GlassFish container. Here are the contents of the glassfish-container.properties file:

   #as.home=/Applications/NetBeans/glassfish-v2.1
   as.home=C:/ProgramFiles/glassfish-v2.1
   lib.home=${as.home}/lib"
   lib.endorsed=${as.home}/lib/endorsed"
   lib.glassfishv3=${as.home}/modules
   domain=domain1
   deploy.dir=${as.home}/domains/${domain}/autodeploy

Here are the contents of the no-container.properties file:

   metro.home=/Users/carr/ws/wsit/wsit/dist/image/metro/lib
   lib.home=${metro.home}
   lib.endorsed=${metro.home}
   lib.glassfishv3=${metro.home}
   deploy.dir=/tmp

The no-container.properties file points directly to the image created when building Metro. This allows users to test the latest bits. In general, most people will not want to download Metro source, but they will want to try building a service without deploying it to a container, that is, simply publishing the service endpoint using the Endpoint.publish() method. In that case, use the GlassFish or Tomcat settings to get the Metro code -- the deploy.dir assignment will be ignored.

Building a Service From Java Classes and Deploying To a Container

Let's take a closer look at what's in the build.xml file and see how it's used. Let's begin by examining the project that builds a Metro web service from a Java class and deploys the service to a container.

First change to the from-java-container directory:

   cd examples/from-java-container

The .README file in this directory shows the ant tasks necessary to build and deploy the service, to run the client, to do cleanup, and to undeploy the service. Here are the contents of the .README file:

   ant service-from-java-container
   ant client
   ant clean
   ant service-undeploy

Building and Deploying the Service

The first ant task in the .README file runs the service-from-java-container target. Here is the definition of that target in the build.xml file:

   <target name="service-from-java-container"
	    depends="clean, setup, setup-war, service-compile, service-wsgen, service-war, service-deploy"/>

The service-from-java-container target initially runs the following targets:

  • clean - Removes any previously generated and/or compiled code.
  • setup - Creates a build/classes directory.
  • setup-war - Creates a build/war directory.
  • service-compile - Compiles the pertinent Java classes.
  • service-wsgen - Runs the wsgen command to generate portable artifacts for the web service.
  • service-war - Creates a WAR file for the compiled and generated code.
  • service-deploy - Deploys the WAR file to the deploy directory of the container.

Here is what the service-compile target looks like:

    <target name="service-compile" depends="setup">
        <javac
            fork="true"
            srcdir="${basedir}/src"
            destdir="${build.classes.home}"
            includes="\*\*/service/\*\*,\*\*/common/\*\*">
            <classpath refid="wsit.classpath"/>
        </javac>
       </target>

The service-compile target runs the javac compiler on all Java classes found in directories named service and common (note that common is not used in this example). A Java class annotated with @WebService is found and compiled (not shown).

Here is what the service-wsgen target looks like:

    <target name="service-wsgen" depends="setup">
        <wsgen
            verbose="${wsgen.verbose}"
            keep="${wsgen.keep}"
            destdir="${build.classes.home}"
            sei="${service.wsgen.sei}"
	       >
            <classpath path="${build.classes.home}"/>
        </wsgen>
    </target>

The service-wsgen target invokes the wsgen command to generate server-side artifacts, such as JAXB marshaling code, for the web service. The target uses wsgen settings from the tools.properties and service.wsgen.sei variables defined in the build.properties file of the current directory. For example, here is the service.wsgen.sei setting in the build.properties file in the from-java-container directory:

    service.wsgen.sei=fromjava.service.AddNumbersImpl

The service.wsgen.sei setting is the service endpoint interface (SEI) for the web service, that is, a Java class that contains a @WebService annotation.

Here are the contents of the service-war target:

    <target name="service-war">
	     <war warfile="${service.war.file}" webxml="etc/web.xml">
	         <webinf dir="${etc.dir}" includes="sun-jaxws.xml"/>

	         <zipfileset
	             dir="${etc.dir}"
	             includes="\*.wsdl, \*.xsd"
	             prefix="WEB-INF/wsdl"/>

	         <classes dir="${build.classes.home}"/>
	     </war>
    </target>

The service-war target creates a WAR file that contains the compiled and generated code. It also includes \*.xml files (and \*.wsdl and \*.xsd files - not used in this example). The name of the WAR file is defined in build.properties of the current directory, as follows:

    service.war.filename=wsit-enabled-fromjava.war

Here is what the service-deploy target looks like:

    <target name="service-deploy">
	        <copy file="${service.war.file}" todir="${deploy.dir}"/>
    </target>

The service-deploy target copies the WAR file to the deploy directory of the container.

Creating the Client

The second ant task in the from-java-container .README file runs the client target. The client target builds the client and then runs it. Building the client runs the client-wsimport, client-compile, and client-cp-config targets in the build.xml file.

Here is the definition of the client-wsimport target:

   <target name="client-wsimport" depends="setup">
        <wsimport
           debug="${wsimport.debug}"
           verbose="${wsimport.verbose}"
           keep="${wsimport.keep}"
           extension="${wsimport.extension}"
           xendorsed="${wsimport.xendorsed}"
           destdir="${build.classes.home}"
           wsdl="${client.wsimport.wsdl}"
   	    package="${client.wsimport.package}"
   	   >
            <binding dir="${etc.dir}" includes="${client.wsimport.binding}"/>
        </wsimport>
    </target>

The client-wsimport target invokes the wsimport command. The target picks up client.wsimport.wsdl, client.wsimport.package, and client.wsimport.binding settings in the build.properties file in the current directory (client.wsimport.binding is not used in this example). Here are the settings in the build.properties file:

   address=http://localhost:8080/wsit-enabled-fromjava/addnumbers
   client.wsimport.wsdl=${address}?wsdl
   client.wsimport.package=fromwsdl.client

The target action retrieves the WSDL file of the deployed service and places the generated code in the fromwsdl.client package. You can implement finer-grained control of JAXB bindings by supplying a client.wsimport.binding file.

Here is the definition of the client-compile target:

   <target name="client-compile" depends="setup">
       <javac
           fork="true"
           srcdir="${basedir}/src"
           destdir="${build.classes.home}"
           includes="\*\*/client/\*\*,\*\*/common/\*\*">
           <classpath>
               <path refid="wsit.classpath"/>
               <pathelement path="${client.compile.classpath.extra}"/>
           </classpath>
       </javac>
   </target>

The client-compile target runs the javac compiler on all the Java source code for the project that it finds in the client and common directories under src.

Here is the definition of the client-cp-config target:

   <target name="client-cp-config" depends="setup">
       <copy todir="${build.classes.home}">
           <fileset dir="${etc.dir}" includes="client-\*.xml"/>
       </copy>
   </target>

The client-cp-config target copies any files to the build directory. This is useful for configuration files such as those used in JAX-WS handler configuration (not used in this example).

Here is the target that runs the client:

   <target name="client-run">
       <java fork="true" classname="${client.main}">
           <jvmarg value="${client.http.dump.do}"/>
           <classpath>
               <path refid="wsit.classpath"/>
               <pathelement location="${build.classes.home}"/>
               <pathelement location="${etc.dir}"/>
           </classpath>
           <arg value="${client.main.arg1}"/>
           <arg value="${client.main.arg2}"/>
       </java>
   </target>

The client-cp-config target defines the main class and optionally defines a switch to control whether Metro dumps the HTTP messages that are sent and received. It also optionally passes values to the main class. The target uses the following settings from the build.properties file:

   client.main=fromjava.client.AddNumbersClient
   #client.http.dump=true

The following ant tasks do final clean up by deleting the build directory and undeploying the service from the container.

   ant clean
   ant service-undeploy

Building a Service From a WSDL File and Deploying To a Container

Now let's examine the project that builds a Metro web service from a WSDL file and deploys the service to a container.

Change to the from-wsdl-container directory:

   cd examples/from-wsdl-container

The ant tasks for building a service from WSDL are similar to those for building a service from a Java class. The difference is that the service-wsimport target runs before the service-compile target, and the service-wsgen target does not run at all.

The service-wsimport target generates code from a WSDL file similarly to the way the client-wsimport target does on the client-side in building a service from a Java class. However, in the service-wsimport case, the WSDL file is located in the file system as defined in the build.properties file, rather than accessed from the network. Here are the settings in the build.properties file in the from-wsdl-container directory:

   service.wsimport.wsdl=etc/AddNumbers.wsdl
   service.wsimport.package=fromwsdl.service

The .README file in the from-wsdl-container directory shows the ant tasks to build a service from a WSDL file and deploy it to a container. With the exception of the initial task, the tasks are the same as for building a service from a Java class and deploying it to a container. The client tasks are exactly the same because the client should know nothing about the implementation of the web service.

Here are the contents of the .README file:

   ant service-from-wsdl-container
   ant client
   ant clean
   ant service-undeploy

Building a Service From a Java Class and Publishing It

So far we've examined projects that build a web service and deploy it to a container. Now let's examine projects that build a service and publish the web service endpoint.

Change to the from-java-publish directory:

   cd examples/from-java-publish

The .README file shows the ant tasks to build and publish the service. Here is the initial task:

   ant service-from-java-publish &

The & at the and of the command is the way to run a UNIX or LINUX process in the background. (Running the process in the background allows me to use the same shell for further commands. However, spawning another shell works well too.)

Here is the definition of the service-from-java-publish target in the build.xml file:

   <target name="service-from-java-publish"
	    depends="clean, setup, service-compile, service-wsgen, service-cp-config, service-publish"/>

As is the case for building a service from a Java class and deploying it to a container, the service-compile target compiles the user-written source code for the web service. The service-wsgen target then runs the wsgen command on the SEI. The service-cp-config target copies service-\*.xml files from the etc directory. Those files are useful for configuration files such as JAX-WS handlers. However, they are not used here.

The service-publish target is where this project differs from service-from-java-container. Here is the definition of the service-publish target:

   <target name="service-publish">
       <java fork="true" classname="${service.main}">
           <jvmarg value="${service.http.dump.do}"/>
           <classpath>
               <path refid="wsit.classpath"/>
               <pathelement location="${build.classes.home}"/>
           </classpath>
           <arg value="${service.main.arg1}"/>
           <arg value="${service.main.arg2}"/>
       </java>
    </target>
The service-publish target runs service.main and passes it jvmargs and main args. The values for these variables are specified in the build.properties file in the from-java-publish directory as follows:

   address=http://localhost:8888/echo

   service.wsgen.sei=foo.service.Echo
   service.main=common.GenericPublisher
   service.main.arg1=${address}
   service.main.arg2=${service.wsgen.sei}

Notice that the service.main class is GenericPublisher. Most of the code to publish an endpoint is the same for the various projects in this example. Because of this, I use a GenericPublisher to do the publishing. I share this class, using symbolic links, will all the projects that publish. The GenericPublisher is given the address at which to publish the service and the SEI to be published. The class uses reflection to instantiate the SEI.

Here are the contents of the GenericPublisher class:

   package common;

   import javax.xml.ws.Endpoint;

   public class GenericPublisher {
       public static void main(String[] av) {
           final String address = av[0];
           final String serviceImplementationBeanClass = av[1];
           try {
               final Object serviceImplementationBean =
                   Class.forName(serviceImplementationBeanClass).newInstance();
               Endpoint.publish(address, serviceImplementationBean);
           } catch (Throwable t) {
               t.printStackTrace();
           }
       }
   }

The steps to run the client are identical to those in the other projects in this example.

Building a Service From a WSDL File and Publishing It

Change to the from-java-publish directory:

   cd examples/from-wsdl-publish

The .README file shows the ant tasks to build and publish the service. Here is the initial task:

   ant service-from-wsdl-publish &

Here is the definition of the service-from-wsdl-publish target in the build.xml file:

   <target name="service-from-wsdl-publish"
	    depends="clean, setup, service-wsimport, service-compile, service-cp-config, service-publish"/>

The first steps in this process are similar to the service-from-java-container. The main differences are the last steps. Instead of creating a WAR file and deploying it in a container, this process publishes the endpoint using the GenericPublisher, as specified in the following build.properties file settings:

   tools.properties=build-common/tools.properties
   container.properties=build-common/no-container.properties

   service.wsimport.wsdl=etc/AddNumbers.wsdl
   service.wsimport.package=fromwsdl.service

   address=http://localhost:8080/fromwsdl/addnumbers
   service.main=common.GenericPublisher
   service.main.arg1=${address}
   service.main.arg2=fromwsdl.service.AddNumbersImpl

   client.wsimport.wsdl=${address}?wsdl
   client.wsimport.package=fromwsdl.client
   client.main=fromwsdl.client.AddNumbersClient

   client.http.dump=true

Again, the client steps are identical.

Summary

This Tech Tip showed a build.xml file that can be used with Metro to create a web service from a Java class or from a WSDL file and deploy the service in a web container or standalone using the Endpoint.publish method.

It is also possible to create dynamic clients using the JAX-WS Dispatch API and dynamic services using the JAX-WS Provider API. Much of the ant build file described in this tip should work for those cases, but I have not tested it or extended it.

I also use NetBeans in combination with GlassFish to create and deploy Metro-based services and clients. But it is often useful to have some test cases handy that can be used quickly from the command line in different configurations. And that's the value of the ant build file covered in this tip.

Further Reading

For more information, see the following resources:

About the Author

Harold Carr is the engineering lead for enterprise web services interoperability at Sun Microsystems. Previous to this role, Harold was responsible for RMI-IIOP load-balancing and fail-over in the Sun Java System Application Server. He designed the core architecture used in Sun's CORBA ORB and in the JAX-RPC 2.0 reference implementation and the scalable socket communications architecture used in SJSAS HTTP and IIOP remoting. Harold helped write the OMG Portable Object Adapter specification and was chairperson of the OMG Portable Interceptor specification. Previous to Sun, he did distributed computing research at Hewlett-Packard Research Laboratories and Schlumberger Research Laboratories, was Chief Architect of Visual Lisp technology at Autodesk, and was a logic simulation consultant for Cirrus Logic. He holds a Ph.D., in Computer Science from the University of Utah. Read Harold Carr's blog.

Comments:

Great work , thanks, but it all looks real complicated at this time of the day :-))

Posted by Payday Loan on August 13, 2009 at 08:56 AM PDT #

Hi,
I first time get to know about the metro and its benefits.I find it very useful for the web-developers.

Posted by externe festplatte on November 09, 2009 at 07:43 PM PST #

Post a Comment:
Comments are closed for this entry.
About

edort

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today