Exposing EJBs as Webservices using JaxWs 2.0

Last modified by Guillaume Sauthier on 2012/03/20 13:25

Objectives

This how-to have the objectives of quickly bootstrap you on JAX-WS webservices creation. It focus on how to expose EJB3 as webservices, and do not explain client view of the web services.

After reading this how to you should be able to:

  • Expose an existing EJB3 as a webservice
  • Generate a WSDL file (+ JAXB required classes) from a Java interface
  • Generate JAX-WS (server-side) standards artifacts (classes, ...) from the WSDL
  • Deploy the web service using JOnAS
  • Access it though its URL

Pre-requisites

This Cookbook only targets JOnAS 5.1 M1!

JOnAS Services activation

Before everything, the 'jaxws' service should be activated on JOnAS startup.

See the 'jonas.services' property in your {JONAS_BASE}/conf/jonas.properties.

jonas.services    registry,jmx,jtm,db,dbm,security,wm,resource,ejb2,ejb3,ws,jaxws,web,ear,depmonitor

If using Ant to generate JAX-WS artifacts (classes + XML)

  In the next versions of JOnAS, theses pre-requisites steps will no more be required, easing JAX-WS development.

The EJB

There are 2 ways to develop a web service, you can start from a Java SIB (Service Implementation Bean) or from an existing WSDL.

The WSDL is an XML format that describe both the service contract (the interface) and technical datas (XSD types, URL of the endpoints, ...) that will help to use the web service.

The Service Implementation Bean is basically your EJB class and a Service Endpoint interface (SEI), both annotated with some JAX-WS annotations.

Starting from Java

The preferred way when starting a JAX-WS endpoint from Java is to start by writing a SEI (Service Endpoint Interface). This interface describes the service that will be exposed to clients.

Once the SEI is ready, It's time to write the Service Implementation Bean (SIB) that is an implementation of the SEI interface.

Service Endpoint Interface

Here, a simple Stock Quote Reporter service interface is described:

@WebService(name="QuoteReporter",
            targetNamespace="http://jonas.ow2.org/examples/jaxws/quote")
public interface QuoteReporter {

 public Quote getQuote(@WebParam(name="ticker") String ticker);
}

the @WebWervice annotation, placed on a SEI, provides the information required to generate a valid WSDL:

  • name: this value will be used as the wsdl:portType name in the generated WSDL
  • targetNamespace: this value will be used as the XML namespace containg your WSDL declaration. For example, the serviceName provided above is "prefixed" with this value ({targetNamespace}serviceName -> {http://easybeans.ow2.org/examples/cxf/greeter}SOAPService). It's required that this value is a URI, it's NOT required to use an URL or that this URL is accessible.

Service Implementation Bean

@Stateless
@WebService(portName="QuoteReporterPort",
            serviceName="QuoteReporterService",
            targetNamespace="http://jonas.ow2.org/examples/jaxws/quote",
            endpointInterface="org.ow2.jonas.examples.jaxws.quote.QuoteReporter",
            wsdlLocation="META-INF/wsdl/QuoteReporterService.wsdl")
public class QuoteReporterBean implements QuoteReporter {

public Quote getQuote(String ticker)  {
return new Quote(ticker, Math.random() * 100);
}
}

You have noticed that there is also a @WebWervice annotation on the SIB.
<p/>
This time, this annotation will provide a value for SIB specific attributes:

  • portName: this value specifies the wsdl:port to be used
  • serviceName: this value specifies the wsdl:service to be used
  • endpointInterface: this value specifies the service endpoint interface classname (SEI)
  • wsdlLocation: this value describe where the JAX-WS container, at runtime, will be able to find the associated WSDL. In this case it's in the standard directory (META-INF/wsdl).
    <p/>
    As this class is also annotated with @Stateless, this component will be deployed as a Bean.

Generating WSDL using Ant tasks

First, declares the CXF namespace in your project element

<project name="JAXWS EJB" basedir="."
        xmlns:cxf="antlib:org.apache.cxf.ant.extensions">

Then, enable the CXF ant tasks in your build:

<property name="cxf.home"
         location="!!! CXF_HOME HERE !!!" />

<path id="cxf.classpath">
 <pathelement location="${cxf.home}/lib/cxf-manifest.jar" />
</path>

<path id="cxf.anttasks.classpath">
 <pathelement location="${cxf.home}/modules/integration/cxf-anttasks-2.0.8.jar" />
 <path refid="cxf.classpath" />
</path>

<taskdef uri="antlib:org.apache.cxf.ant.extensions"
        resource="org/apache/cxf/ant/extensions/antlib.xml"
        classpathref="cxf.anttasks.classpath" />

Now you can use the Java 2 WSDL ant task.
This task will generate for you, from the SEI:

  • A WSDL file (describing the XML contract)
  • Plus a XML Schema files describing the types that will be exchanged on the wire
  • Plus some JAXB 2.0 classes
<target name="java2wsdl" depends="compile">
 <mkdir dir="${build.generated.dir}"/>

 <property name="sei.classname"
           value="org.ow2.jonas.examples.jaxws.quote.QuoteReporter"/>

 <echo level="info" message="Generating WSDL from Service Endpoint Interface (${sei.classname}) ..."/>
 <cxf:java2wsdl sei="${sei.classname}"
                resourceDestDir="${build.generated.dir}">
   <classpath>
     <pathelement location="${build.classes.dir}"/>
   </classpath>
 </cxf:java2wsdl>
</target>

Remember that this is the SEI that is used for WSDL generation, NOT the SIB !

Starting from WSDL

Starting a endpoint development from WSDL means that you have a WSDL that describes your service contract before having your EJB code.

WSDL2Java when developing an endpoint is a 1 time operation. You don't want your customized SIB to be overwritten by CXF 

The idea here is to generate as much JAX-WS artifacts as possible from a given WSDL.

CXF provides a WSDL2Java ant task that will generate the SEI interface, a skeleton of a SIB (although it's not yet an EJB) plus all the JAXB artifacts that the Java2WSDL ant tasks is also generating:

  • {portType}.java that is the SEI
  • {portType}Impl.java that is the SIB
  • JAXB annotated classes + ObjectFactory

Some generated classes are only useful on the client side (the one who will use the service), so theses generated classes can be safely suppressed:

  • {service}.java
  • {portType}_{port}_Server.java is an helper class for Java SE environments

wsdl2java-artifacts.png

Implementing the SIB

First thing to do, you may rename your <portType>Impl.java to a more EJB name (like XYZBean.java), but that's up to you ! At least, doing so will prevent your EJB to be overwritten if an accidental 2nd attempt to wsdl2java is made.

/**
 * This class was generated by Apache CXF 2.0.8
 * Fri Oct 10 15:04:06 GMT+01:00 2008
 * Generated source version: 2.0.8
 *
 */

@javax.jws.WebService(name="HelloWorldType",
                      serviceName="HelloWorldService",
                      portName="HelloWorldPort",
                      targetNamespace="http://jonas.ow2.org/examples/jaxws/greeter",
                      wsdlLocation="file:etc/wsdl/HelloWorld.wsdl",
                      endpointInterface="org.ow2.jonas.examples.jaxws.greeter.HelloWorldType")
public class HelloWorldTypeImpl implements HelloWorldType {

   private static final Logger LOG = Logger.getLogger(HelloWorldTypeImpl.class.getName());

   /* (non-Javadoc)
     * @see org.ow2.jonas.examples.jaxws.greeter.HelloWorldType#hello(java.lang.String  name )*
     */

   public java.lang.String hello(java.lang.String name) {
        LOG.info("Executing operation hello");
        System.out.println(name);
       try {
            java.lang.String _return = "";
           return _return;
       } catch (Exception ex) {
            ex.printStackTrace();
           throw new RuntimeException(ex);
       }
   }

If you look at the SIB, you will discover that it is very similar to the SIB that was produced with the "Java first" approach. At least, the JAX-WS annotations are here.

Concerning the @WebService annotation, the only change to do is to change the wsdlLocation value in order to match the location of the WSDL in the soon to be created EjbJar file (META-INF/wsdl instead of etc/wsdl in my case).

All you have to do is to implement the real behavior of the method (hello() should return something better than an empty string), and declare your class as an EJB.

@Stateless                   //    <-------- EJB3 Annotation
@WebService(name="HelloWorldType",
            serviceName="HelloWorldService",
            portName="HelloWorldPort",
            targetNamespace="http://jonas.ow2.org/examples/jaxws/greeter",
            wsdlLocation="META-INF/wsdl/HelloWorld.wsdl", //  <----- Updated
           endpointInterface="org.ow2.jonas.examples.jaxws.greeter.HelloWorldType")
public class HelloWorldTypeImpl implements HelloWorldType {

   public String hello(String name) {
     return "Hello " + name;      //      <------ Implemented
   }
}

Runtime

Portability

One important thing to understand when writing JAX-WS web services is that all the generated artifacts (JAX-WS SEI, SIB and JAXB classes) are portable accros application servers.

That means, that the EjbJar you're about to deploy on JOnAS 5.1 is also ready to be deployed on any other Java EE 5 compatible application server.

Deployment

As usual in JOnAS, deployment is super simple: simply drop the ejbjar in the ${jonas.base}/deploy/ directory.

After a few seconds, similar traces should appear on the console:

Deploying 'EJB3DeployableImpl[archive=/home/sauthieg/jonasbases/jb.50/deploy/quote.jar]'...
Internal EasyBeans Bus started ...
Creating Service {http://jonas.ow2.org/examples/jaxws/quote}QuoteReporterService from WSDL: jar:file:/home/sauthieg/jonasbases/jb.50/deploy/quote.jar!/META-INF/wsdl/QuoteReporterService.wsdl
PortMetaData [context:quote, pattern:/QuoteReporterService/QuoteReporterPort]
Setting the server's publish address to be http://localhost:9090/hello
...
Endpoint org.ow2.jonas.examples.jaxws.quote.QuoteReporterBean inited
Container '/home/sauthieg/jonasbases/jb.50/deploy/quote.jar' [1 SLSB, 0 SFSB, 0 MDB] started in 10 592 ms
'EJB3DeployableImpl[archive=/home/sauthieg/jonasbases/jb.50/deploy/quote.jar]' EJB3 Deployable is now deployed

Look closely at the PortMetaData line, it will give you all the necessary information to access your endpoint:

Endpoint URL is: http://localhost:9000/{ejbjar-name}/{service-name}/{port-name}

If you hit the right spot, the following message should appear: "There is a service here, but it's only accessible with POST method."

Testing

Testing is an important phase of the application development, and testing your new webservice is a need !

SoapUI is a very helpful application to help web services testing. Furthermore, it has a Java Web Start launcher.

  • Starts by creating a "new WSDL Project"
  • It needs the component WSDL that you should have somewhere in your build directory (generated by Java2WSDL or you already had it)
  • SoapUI will generate empty requests for all the declared operations
  • When opening a Request, DO NOT FORGET to update the endpoint URL value (on the top), because the endpoint location in the WSDL has not been updated
    • This is were you use the URL endpoint value we've just seen above
  • Then you can change the XML content to perform whatever test you want
    • Look at the '?' in the XML, they denotes places where you can write something
  • Press the "play" button on the left to invoke your webservice and see the result.

soapui-quote.png

Conclusion

In this quick how-to, it has been explained how to expose an existing EJB3 as a webservice (using the WSDL first approach and the Java first approach) with the help of CXF (mainly ant tasks, but maven plugins exists and command line is also supported). The deployment of the web service in JOnAS have been described, and the testing of the endpoint using the SoapUI application have been introduced.

Resources

  • Apache CXF 2.0 is an ASL 2.0 implementation of JAX-WS 2.0
  • Apache CXF Generation Tools
    • WSDL2Java
      • Starting from a given WSDL file and generate client and/or server side standards artifacts
      • Client side artifacts can be used as-is
      • Server side artifacts are skeletons that needs to be filled up
    • Java2WSDL
      • Starting from a Java interface definition (SEI) and generates the server side standard artifacts
      • Server side artifacts are skeletons that needs to be filled up
Tags:
Created by sauthieg on 2008/09/05 09:50
 
Oracle and Java are registered trademarks of Oracle and/or its affiliates.
OSGi is a trademark, registered trademark, or service mark of The OSGi Alliance in the US and other countries.
Copyright © 2006-2012, OW2 Consortium,
Licensed under CC Attribution-ShareAlike 2.0.