CHAPTER 12 Web Services

Andy Oliver, Scott Stark

This chapter discusses the support for web services in JBoss. Both EJBs and MBeans may be exposed as web services that are accessible via the standard SOAP 1.1 over HTTP protocol for use by Java and non-Java clients. Support for web services is provided via Apache Axis version 1.1. Axis is used to handle the SOAP details and JBoss provides custom deployers and integration. Additionally, a JBoss.NET tag library is provided for XDoclet to make integration smoother. A standard JBoss.NET web service consists of a stateless session bean and a set of metadata describing its web service properties.

XDoclet

XDoclet has became a popular choice for EJB development; however, XDoclet is not limited to EJB development. The XDoclet approach is known as "Attribute Oriented Programming." Often it is confused with "Aspect Oriented Programming;" however, while the two are related, they are not the same. Attribute Oriented Programming involves marking up source code to give it greater meaning. Aspect Oriented Program involves applying that meaning to code. While it can be said that aspects are usually applied to attributes and therefore the two often go hand in hand, one does not necessitate the other.

For the purpose of this chapter we'll assume that you are using XDoclet already in your software development. Our purpose is to merely extend your XDoclet EJB Development knowledge with JBoss.NET uses. You can read more about XDoclet at http://xdoclet.sourceforge.net or in the book "XDoclet in Action" by Craig Walls and Norman Richards. (Manning, 2003)

Installing XDoclet

The book examples archive comes with XDoclet, and its jars are located in the examples/lib directory, so you may skip this section if you have the examples archive. If you do not have the book examples archive, installation of XDoclet starts with downloading XDoclet from SourceForge. To obtain XDoclet, navigate to http://xdoclet.sourceforge.net and click on downloads. From there you can select the mirror closest to you. This explanation is based on version bundled with the jboss-3.2.3 source distribution and it is suggested that you use this or a later version.

Next, you will copy the jboss-net-xdoclet-module.jar from the JBOSS_DIST/client into the lib subdirectory of your xdoclet installation. Lastly, you will need a 3.2.1 or latter version of JBoss in order to obtain the required J2EE dependencies of jboss-net XDoclet.

A Hello World EJB as a Web Service

In this section we will go through the steps to make an EJB a web service. The code used here is located in the src/main/org/jboss/chap12 directory of the examples archive. Consider the EJB code, complete with XDoclet markup shown in See A HelloWorld EJB with XDoclet markup..

A HelloWorld EJB with XDoclet markup

/*

* JBoss, the OpenSource J2EE webOS

*

* Distributable under LGPL license.

* See terms of license at gnu.org.

*/

package org.jboss.chap12.hello;

 

import javax.ejb.EJBException;

 

/**

* The typical Hello Session Bean this time

* as a web-service.

* @author jung

* @version $Revision: 1.1 $

* @ejb:bean name="Hello"

* display-name="Hello World Bean"

* type="Stateless"

* view-type="remote"

* jndi-name="Hello"

* @ejb:interface remote-class="org.jboss.chap12.hello.Hello" extends="javax.ejb.EJBObject"

* @ejb:home remote-class="org.jboss.chap12.hello.HelloHome" extends="javax.ejb.EJBHome"

* @ejb:transaction type="Required"

*/

public class HelloBean

extends BaseSession implements javax.ejb.SessionBean

{

/**

* @ejb:interface-method view-type="remote"

*/

public String hello(String name)

{

return "Hello "+name+"!";

}

 

/**

* @ejb:interface-method view-type="remote"

*/

public Object[] complexHello(Object[] query)

{

Object[] reply = new Object[query.length];

for(int n = 0; n < query.length; n ++)

{

HelloObj hello = (HelloObj) query[n];

System.out.println("hello, "+hello.getMsg());

reply[n] = new HelloReplyObj(n+": "+hello.getMsg());

}

return reply;

}

}

The BaseSession class merely implements the EJB life cycle methods that we have left out of this example. Suppose that this session bean is an essential part of an application, and let us also imagine that we have landed a deal which will allow us to provide greetings for other clients in a B2B arrangement. This customer may or may not be using Java, and it is not possible to use RMI/IIOP or RMI/JRMP to invoke the EJBs directly.

Thus, we have decided to use web services to expose our EJB. We want to include both the hello and complexHello methods in the web service exposed by this EJB. To do this, each method needs to be marked with a @jboss-net:web-method tag in the method javadoc comment:

/**

* @jboss-net:web-method

* @ejb:interface-method view-type="remote"

*/

public String hello(String name)

{

...

}

 

/**

* @jboss-net:web-method

* @ejb:interface-method view-type="remote"

*/

public Object[] complexHello(Object[] query)

{

...

}

Now these methods will be exposed as part of the web service interface, but we also need to mark the EJB as a web service and declare a URN for it. It is suggested that the URN match your ejb-name. To indicate that an EJB should be exposed as a web service, add a @jboss-net:web-service tag to the class level javadoc comment, and include a urn="MyWebServiceName" to define the URN of the web service. For example:

/**

...

* @ejb:bean name="Hello"

* display-name="Hello World Bean"

* type="Stateless"

* view-type="remote"

* jndi-name="Hello"

...

* @jboss-net:web-service urn="Hello"

*/

public class HelloBean

Declaring How to Handle Non-simple Types

The hello method returns Java primitives types. This requires no special configuration in order for the jboss-net layer to handle it. However, the complexHello method accepts an Object[] array of HelloObj types and returns an Object[] array of HelloReplyObj types. These types must be marked as custom types and the class that knows how to serialize/deserialize them to XML declared. This is done using the @jboss-net.xml-schema xdoclet tag. See The non-simple types used by the complexHello method. shows the custom classes and the jboss-net tag usage.

The non-simple types used by the complexHello method

package org.jboss.chap12.hello;

 

/** A custom data object class that needs to specify a custom serializer

*

* @jboss-net.xml-schema urn="hello:HelloObj"

*/

public class HelloObj implements java.io.Serializable

{

private String msg;

 

public HelloObj(String msg)

{

this.msg = msg;

}

 

public String getMsg()

{

return this.msg;

}

}

 

package org.jboss.chap12.hello;

 

/** A custom data object class that needs to specify a custom serializer

*

* @jboss-net.xml-schema urn="hello:HelloReplyObj"

*/

public class HelloReplyObj implements java.io.Serializable

{

private String msg;

 

public HelloReplyObj(String msg)

{

this.msg = msg;

}

 

public String getMsg()

{

return this.msg;

}

}

The urn parameter specifies the namespace the custom type will be defined under.

Writing the Ant build.xml file

We have added everything we need in order to mark our EJB as a JBoss.NET web service. Now we need to create an Ant build.xml that will invoke XDoclet in order to create our build artifacts such as the home and remote interfaces as well as the ejb-jar.xml and web-service.xml descriptors. The first thing to do in the build.xml is set up some environment properties that define the key locations in the src and build tree. The following fragments are from the examples/src/main/org/jboss/chap12/build.xml file of the examples archive. Before making using of XDoclet based tasks, we need to prepare for XDoclet by telling Ant where the xdoclet jars are located. Step one is creating a classpath for xdoclet:

<path id="xdoclet.classpath">

<pathelement location="${lib.dir}/commons-collections.jar"/>

<pathelement location="${lib.dir}/xdoclet-module-jboss-net.jar"/>

<pathelement location="${lib.dir}/xdoclet-jb3.jar"/>

<pathelement location="${lib.dir}/xdoclet-xjavadoc-jb3.jar"/>

<pathelement location="${lib.dir}/xdoclet-ejb-module-jb3.jar"/>

<pathelement location="${lib.dir}/xdoclet-jboss-module-jb3.jar"/>

<pathelement location="${lib.dir}/xdoclet-jmx-module-jb3.jar"/>

<pathelement location="${lib.dir}/xdoclet-web-module-jb3.jar"/>

<pathelement location="${jboss.dist}/client/jbossall-client.jar"/>

<pathelement location="${jboss.dist}/client/log4j.jar"/>

<pathelement location="${jboss-net.sar}/commons-logging.jar"/>

</path>

We'll have to reference this when we declare the ejbdoclet task (explained later). Now we can create our compile-src task which relies on xdoclet:

The xdoclet target definition

<target name="compile-src" depends="config">

<!-- Generate the xdoclet source -->

<property name="xdoclet.classpath" refid="xdoclet.classpath"/>

<echo>xdoc target</echo>

<echo message="${xdoclet.classpath}"/>

<taskdef

name="ejbdoclet"

classname="xdoclet.modules.ejb.EjbDocletTask"

classpathref="xdoclet.classpath"

/>

 

<tstamp>

<format property="TODAY" pattern="d-MM-yy"/>

</tstamp>

 

<ejbdoclet

destdir="${build.src.dir}"

excludedtags="@version,@author"

addedtags="@xdoclet-generated at ${TODAY}"

ejbspec="2.0"

>

<fileset dir="${src.dir}">

<include name="org/jboss/chap12/hello/*Bean.java"/>

<include name="org/jboss/chap12/hello/*Obj.java"/>

</fileset>

 

<packageSubstitution packages="implementation"

substituteWith="interfaces"/>

 

<remoteinterface pattern="{0}"/>

 

<localinterface pattern="{0}Local"/>

 

<homeinterface/>

<localhomeinterface/>

 

<deploymentdescriptor destdir="${chapter.metainf.dir}"/>

<jbossnet webDeploymentName="HelloBean"

prefix="hello"

destdir="${chapter.metainf.dir}"

targetNameSpace="http://localhost/HelloBean"/>

</ejbdoclet>

 

<java dir="${build.src.dir}" fork="yes" failOnError="true"

className="org.apache.axis.wsdl.WSDL2Java">

<!-- Map the data object to the org.jboss.chap12.hello.xml pkg -->

<arg value="-Nhttp://localhost/HelloBean=org.jboss.chap12.hello.xml"/>

<!-- Map the data object to the org.jboss.chap12.hello.xml pkg -->

<arg value="-Nhttp://localhost:8080/jboss-net/services/Hello=org.jboss.chap12.hello.xml"/>

<!-- Retrieve the wsdl from the deployed ejb -->

 

<arg value="${src.resources}/hello.wsdl"/>

<classpath refid="axis.path"/>

</java>

</target>

This gives us the basis for compiling EJBs and running xdoclet. Lets analyze this closer. The first thing we do is declare the ejbdoclet task definition. Notice that we passed in the xdoclet.classpath to the classpathref that we declared previously. Next we declare a timestamp property. We'll use this in our ejbdoclet task directly below in order to stamp our xdoclet generated artifacts with a message indicating when they were generated. Notice that the rest of our target is encapsulated within the ejbdoclet task. The destdir attribute specifies the output/gen-src directory through the build.src.dir inherited from the top level build.xml file. We have specifically excluded some commonly used javadoc tags from being processed using the excludedtags attribute. We have added a timestamp with the added tags attribute and then specified that we wish to be compliant with the EJB 2.0 spec via the ejbspec directive.

Embedded within this is the fileset task. This task is native to ant and in this context specifies which files we wish to process using xdoclet. Notice that we are only processing the org/jboss/chap12/hello/*Bean.java and org/jboss/chap12/hello/*Obj.java classes. Only those files with Bean.java or Obj.java suffix residing in src/main/org/jboss/chap12/hello will be processed by ejbdoclet. We need to follow a convention such as this to properly control what XDoclet processes. We could use packages or explicitly declare all of the file names, but this is more flexible. We do not want BaseSession , for instance, to be processed via XDoclet; however, it is essential that any objects that we will return from a web service be processed via XDoclet. Thus any objects which need to be serialized are named *Obj.java and all beans are *Bean.java.

Next, we specify the packageSubstitution subtask. A common convention among EJB developers is to divide the implementation (Bean classes) from their interfaces (Remote/Local/Home/LocalHome) via a package. Since xdoclet is generating our interfaces, it needs to know if we use this convention. You can safely leave this out if you do not follow this convention.

Next we have the remote, local, home and local home interface subtasks which operate off of your attributes (view-type, etc). Notice the pattern="{0}" and "{0}Local" attribute/value pairs. The {0} refers to the fileset. Thus if your bean is named "HelloBean", the remote interface would be "Hello" and the local interface would be named "HelloLocal".

The deployment descriptor subtask will generate your deployment descriptors. Notice that we have pointed it to your build/META-INF directory. This will generate your ejb-jar.xml file.

Lastly, the subtask which the jboss-net-xdoclet-module.jar adds, jbossnet is used to generate the web service descriptor. The webDeploymentName attribute specifies the name of the Axis deployment and maps to the web-service.xml deployment element name attribute. The prefix attribute specifies the prefix portion of the XML namespace used for custom application types, and the targetNameSpace attribute defines the associated URI. The destdir is pointing to your build/META-INF directory and is the location to which the web-service.xml descriptor will be written.

For additional information on the ejbdoclet task, consult the reference page: http://xdoclet.sourceforge.net/ant/xdoclet/modules/ejb/EjbDocletTask.html .

The next step is to add the targets to create the ejb-jar and web service archives. The ant task for creating the ejb-jar.jar file is:

<target name="ejb-jar" depends="config"

description="creates the ejb jar file">

<mkdir dir="${chapter.dir}"/>

<jar destfile="${chapter.dir}/hello-ejb.jar"

basedir="${build.classes.dir}">

<metainf dir="${chapter.metainf.dir}">

<include name="ejb-jar.xml"/>

</metainf>

<include name="org/jboss/chap12/hello/**" />

</jar>

</target>

It takes the classes in the output/classes dir under the org.jboss.chap12.hello package as well as the ejb-jar.xml descriptor generated by the ejbdoclet task.

The jbossnet subtask of the ejbdoclet task created a web-service.xml. This file contains the web service descriptor for Apache Axis. To define the web service to jboss-net a web service archive (WSR) needs to be created. A WSR is just an bare java archive with the web-service.xml descriptor in the jar META-INF directory. The target to create the wsr is:

<target name="ejb-wsr" depends="ejb-jar"

description="creates the ejb wsr file for jb.net">

<jar destfile="${chapter.dir}/hello-ejb.wsr">

<metainf dir="${chapter.metainf.dir}">

<include name="web-service.xml"/>

</metainf>

</jar>

</target>

Now we need to package the ejb-jar and wsr into an EAR file. The ear contains these two archives along with the standard EAR application.xml descriptor. The ear target definition is:

<target name="chap12-ex1-ear" depends="ejb-wsr"

description="creates an ear file containing the correct files for jboss.net">

<ear destfile="${chapter.dir}/chap12-ex1.ear"

appxml="${src.root}/application.xml">

<fileset dir="${chapter.dir}">

<include name="hello-ejb.jar"/>

<include name="hello-ejb.wsr"/>

</fileset>

</ear>

</target>

The chap12 build creates a custom JBoss configuration that targeted to just EJBs and web services, and this is called chap12. You will find a server/chap12 configuration file set under the JBOSS_DIST directory after building the examples. Start JBoss using the chap12 configuration and then run the following to deploy the ejb and web service, and access it via a soap client:

[starksm@banshee9100 examples]$ ant -Dchap=chap12 -Dex=1 run-example

Buildfile: build.xml

...

run-example:

 

run-example1:

[echo] Waiting for 5 seconds for deploy...

[java] hello.hello(hello argument)

[java] output:Hello hello argument!

[java] 0: Hello index 0

[java] 1: Hello index 1

[java] 2: Hello index 2

 

 

 

BUILD SUCCESSFUL

Total time: 10 seconds

The server console output will look similar to:

13:34:18,812 INFO [MainDeployer] Starting deployment of package: file:/C:/tmp/JBoss/jboss-3.2.3/server/chap12/deploy/chap12-ex1.ear

13:34:18,812 INFO [EARDeployer] Init J2EE application: file:/C:/tmp/JBoss/jboss-3.2.3/server/chap12/deploy/chap12-ex1.ear

13:34:19,078 INFO [EjbModule] Deploying Hello

13:34:19,234 INFO [StatelessSessionInstancePool] Started jboss.j2ee:jndiName=Hello,plugin=pool,service=EJB

13:34:19,234 INFO [StatelessSessionContainer] Started jboss.j2ee:jndiName=Hello,service=EJB

13:34:19,234 INFO [EjbModule] Started jboss.j2ee:module=hello-ejb.jar,service=EjbModule

13:34:19,234 INFO [EJBDeployer] Deployed: file:/C:/tmp/JBoss/jboss-3.2.3/server/chap12/tmp/deploy/tmp33528chap12-ex1.ear-contents/hello-ejb.jar

13:34:19,296 INFO [EARDeployer] Started J2EE application: file:/C:/tmp/JBoss/jboss-3.2.3/server/chap12/deploy/chap12-ex1.ear

13:34:19,296 INFO [MainDeployer] Deployed package: file:/C:/tmp/JBoss/jboss-3.2.3/server/chap12/deploy/chap12-ex1.ear

13:35:09,312 INFO [JaasSecurityManagerService] Created securityMgr=org.jboss.security.plugins.JaasSecurityManager@42a818

13:35:09,328 INFO [JaasSecurityManagerService] setCachePolicy, c=org.jboss.util.TimedCachePolicy@178b64b

13:35:09,328 INFO [JaasSecurityManagerService] Added other, org.jboss.security.plugins.SecurityDomainContext@83020 to map

13:35:09,843 INFO [STDOUT] hello, Hello index 0

13:35:09,843 INFO [STDOUT] hello, Hello index 1

13:35:09,843 INFO [STDOUT] hello, Hello index 2

If you do not have a WSDL descriptor, you can obtain one WSDL descriptor from the jboss-net service deployment. For the Hello webservice, the jboss-net framework makes the WSDL descriptor available at http://localhost:8080/jboss-net/services/Hello?wsdl. The "http://localhost:8080/jboss-net/services" location is where the jboss-net framework installs a servlet which handles queries about web services that have been deployed. To query for the WSDL descriptor for a webservice, you append the service name as defined by the urn property passed to the EJB class @jboss-net:web-service tag, along with the "?wsdl" suffix.1

Using the WSDL, we can create a client using Apache's Axis library. There is a tool called WSDL2Java which will generate Java client stubs to access the service described by the WSDL descriptor. This was done as part of the config target and we'll look at the usage in the next section.

Creating a Web Service Client with Apache Axis

The WSDL2Java tool is contained in the Axis jars bundled with the jboss-net.sar found in the deploy directory. The chap12 build.xml file contains an Ant scriptlet to create the Axis client stubs using WSDL2Java and the hello.wsdl found in the examples/src/resources directory. The client stubs generation fragment is:

<java dir="${build.src.dir}" fork="yes" failOnError="true"

className="org.apache.axis.wsdl.WSDL2Java">

<!-- Map the data object to the org.jboss.chap12.hello.xml pkg -->

<arg value="-Nhttp://localhost/HelloBean=org.jboss.chap12.hello.xml"/>

<!-- Map the data object to the org.jboss.chap12.hello.xml pkg -->

<arg value="-Nhttp://localhost:8080/jboss-net/services/Hello=org.jboss.chap12.hello.xml"/>

<!-- Use the wsdl for the deployed ejb -->

 

<arg value="${src.resources}/hello.wsdl"/>

<classpath refid="axis.path"/>

</java>

 

When we generated the web-service.xml descriptor using the jbossnet task, we specified the target namespace to have a URI of "http://localhost/HelloBean". This is used for the custom types of the web service. The EJB services interface and support classes are associated with a URI of "http://localhost:8080/jboss-net/services/Hello" in the generated WSDL file. Unless we specify how to map these namespaces onto Java packages, a default transformation will be used. The two -Nxxx values passed to the WSDL2Java command in the target map both URIs to the org.jboss.chap12.hello.xml package. The last argument is the URL from which we can obtain the WSDL descriptor for the Hello web service. The WSDL2Java usage show here generates the files shown in See The web service files generated by the WSDL2Java program.. These files represent the mapping of the web service to Java.

 

FIGURE 12-1. The web service files generated by the WSDL2Java program

The Hello interface provides the Java representation of the web service. The HelloServiceLocator is the factory through which clients obtain a Hello instance. The remaining classes are used by the implementation layer handle the soap transport and object bindings. A sample client based on this generated code is shown in See An Axis Client for the Hello EJB/Web Service.

An Axis Client for the Hello EJB/Web Service

package org.jboss.chap12.client;

 

import org.jboss.chap12.hello.xml.HelloObj;

import org.jboss.chap12.hello.xml.HelloReplyObj;

import org.jboss.chap12.hello.xml.Hello;

import org.jboss.chap12.hello.xml.HelloServiceLocator;

 

/**

* @author [email protected]

* @version $Revision: 1.1 $

*/

public class HelloClient

{

public static void main(String[] args) throws Exception

{

HelloServiceLocator locator = new HelloServiceLocator();

Hello hello = locator.getHello();

System.out.println("hello.hello(" + args[0] + ")");

System.out.println("output:" + hello.hello(args[0]));

Object[] query = new Object[3];

HelloObj ho = new HelloObj();

ho.setMsg("Hello index 0");

query[0] = ho;

ho = new HelloObj();

ho.setMsg("Hello index 1");

query[1] = ho;

ho = new HelloObj();

ho.setMsg("Hello index 2");

query[2] = ho;

Object[] reply = hello.complexHello(query);

for(int n = 0; n < reply.length; n ++)

{

HelloReplyObj r = (HelloReplyObj) reply[n];

System.out.println(r.getMsg());

}

}

}

 


1. Note that the WSDL descriptor generated currently includes the type definitions for all services deployed, not just the types associated with the queried service. This is a bug that can cause problems if you have the same type name deployed under different namespaces. You would need to edit the resulting WDSL descriptor to remove the extra types in this situation.


© 2002-2004 JBoss Inc. All rights reserved.