How-To
Develop a JAX-WS Service
From a Web Service Description
Language (WSDL) Document
First
Publication: 03-May-2007
Author: Tugdual Grall
Introduction
The Oracle Application Server
provides the facilities for generating a web service from an existing
Web Service Description Language (WSDL) document. This
top-down approach, although not as simple as the bottom-up approach
(exposing an existing class as a web service), is preferable for a
number of reasons:
- Careful consideration of the
operations and messages you will
expose leads to much more usable and maintainable web services.
- Implementation artifacts are
much less likely to be exposed by
the web service, and
- Interoperability with other
web services is much more likely
given that the developer has much more control over the WSDL.
The process of generating a web
service from a WSDL document and creating an associated client
application involves:
- Generating a service
endpoint interface (SEI) from the
WSDL. This interface exposes the
functionality and operations defined in the WSDL, using Java
annotations.
- Creating a service
implementation comprised of an SEI
implementation class and supporting classes. These classes
simply
provide implementations for each of the methods declared in the
service interface. In this example the implementation class
will
instantiate, and delegate method calls to, an underlying bank object.
- Packaging the service as an
application module. The service
classes and all associated generated artifacts are packaged up as
standard J2EE deployment modules. For simplicity, this application will
be composed of a Web application (WAR file)
- Deploying
the
service. The service module is deployed to
the Oracle Application Server.
- Generating the
client
proxies. Client proxies/stubs are
generated from the WSDL file for the deployed service.
- Writing
a client
application. An application is written
that leverages the client proxies to invoke remote service operations.
Admittedly, the argument could
be made that it may be simpler to simply modify the underlying
implementation class and expose it directly as a web service by having
it implement the SEI (removing the requirement for creating the service
implementation class as noted above). Although that is a
possibility the more general, and potentially more frequent, case is
that the existing class can not be modified. Rather, the
functionality provided by the existing class needs to be exposed to
remote clients without alteration to the target class. It is
this scenario we will explore in the following sample.
What
are the
Prerequisites?
What
Should You Know?
- Some familiarity with the
standard J2EE deployment model.
What
are the Software
Requirements?
- Oracle Containers for Java EE (11.1.1.0.0)
Technology Preview, available from OTN
- Sun's JDK 1.5,
available here
What
are the Notations?
The following conventions are
used throughout the document:
- %ORACLE_HOME%
- The directory where you installed OC4J or Oracle Application Server
- %JAVA_HOME%
- The directory where your JDK is installed
How
to Build the Application?
Let's take a closer look at the
steps involved in creating the web service application from an existing
Java class. These steps have already been performed for this
sample but are provided for illustrative purposes.
The
Existing Bank
Implementation
The BankMemDB class is an
implementation of the Bank interface and provides standard banking
functionality:
public class BankMemDB implements Bank{
public static BankMemDB newInstance(){}
public String addNewAccount(String name, float initBalance){}
public Account getAccount(String id) {}
public List getAccounts() {}
}
This
class is the existing
class whose functionality we wish to provide access to via a web
service interface.
Exposing
the Bank as a web
service
In order to expose an instance
of the existing Bank as a web
service using the top-down development approach we will:
1.Generate a service interface
from the service WSDL. The complete
WSDL file for can be found here.
The following portion of the WSDL file shows the operations that end up
in the SEI:
<portType name="BankService">
<operation name="getBalance">
<input message="tns:getBalanceInput" />
<output message="tns:getBalanceOutput" />
<fault name="AccountException" message="tns:AccountException"/>
</operation>
<operation name="getAccountID">
<input message="tns:getAccountIDInput" />
<output message="tns:getAccountIDOutput" />
<fault name="AccountException" message="tns:AccountException"/>
</operation>
<operation name="deposit">
<input message="tns:depositInput" />
<output message="tns:depositOutput"/>
<fault name="AccountException" message="tns:AccountException"/>
</operation>
<operation name="withdraw">
<input message="tns:withdrawInput" />
<output message="tns:withdrawOutput"/>
<fault name="AccountException" message="tns:AccountException"/>
</operation>
<operation name="createAccount">
<input message="tns:createAccountInput"/>
<output message="tns:createAccountOutput"/>
<fault name="AccountException" message="tns:AccountException"/>
</operation>
</portType>
The
service
interface generated
from the WSDL represents the functionality and operations we wish to
provide to client applications:
@WebService(targetNamespace="http://howto.jaxws.topdown.service/", name="BankService")
public interface BankService
{
@WebMethod
@ResponseWrapper(targetNamespace="http://howto.jaxws.topdown.service/",
className="howto.jaxws.topdown.service.generated.types.GetBalanceResponse",
localName="getBalanceResponse")
@RequestWrapper(targetNamespace="http://howto.jaxws.topdown.service/",
className="howto.jaxws.topdown.service.generated.types.GetBalance",
localName="getBalance")
@Action(input="http://howto.jaxws.topdown.service/BankService/getBalance/Request", fault =
{
@FaultAction(value="http://howto.jaxws.topdown.service/BankService/getBalance/Fault/AccountException",
className = AccountException.class)
} , output="http://howto.jaxws.topdown.service/BankService/getBalance/Response")
@WebResult(targetNamespace="")
public float getBalance(@WebParam(targetNamespace="", name="acctID")
String acctID, @WebParam(targetNamespace="", name="acctName")
String acctName)
throws AccountException;
...
...
}
You
will note that this service interface use the @WebService annotation
at the class level and other JAX-WS annotations at method and
parameters level to define the service operations and fault. All these
annotations and values are used by the container to generate proper
WSDL and SOAP messages.
2. Create a service
implementation. The implementation class
(BankServiceImpl) implements the service interface (BankService) and
delegates
various calls to a Bank instance it creates.
@WebService( endpointInterface="howto.jaxws.topdown.service.generated.BankService", serviceName="BankService")
public class BankServiceImpl implements BankService{
...
...
public float getBalance(String acctID, String acctName) throws
AccountException {
Account theAccount = m_bank.getAccount(acctID);
if(theAccount == null){
throw new AccountException("No account found for " + acctID,null);
}
return theAccount.getBalance();
}
...
...
}
You
will note that the implementation class uses the @WebService annotation
to define the endpoint interface to use to define the service. This
annotation is used during deployment to define the behavior of the Web
Service itself, for example the name of the operations, parameters and
namespaces, that are in this example extract from the WSDL during the
generation of the interfaces.
Creating
a client application
Once the service has been
created and deployed a client application that leverages the service
can be created from the WSDL file generated as part of the service
generation process (see Generating,
Compiling, and Deploying the Application).
The Oracle Application Server's client generation
tool creates, in
addition to the classes required by the JAX-RPC runtime, a convenience
class that shields the developer from some of the more mundane JAX-RPC
service instantiation tasks. This class, referred to as a
utility client, is leveraged to invoke methods on the remote service by
the banking application (BankApplication) (m_endpoint
is the class attribute for the utility client):
void
demoGoodAccount() throws Exception {
String accountID = m_endpoint.createAccount(DEMO_USER1,2000.50f);
// ...
print statements removed for clarity ...
m_endpoint.deposit(accountID,500.50f);
float balance = m_endpoint.getBalance(accountID,DEMO_USER1);
System.out.println("Current balance is now " + balance);
System.out.println("Withdrawing $250.00 from account");
m_endpoint.withdraw(accountID,250.00f);
balance = m_endpoint.getBalance(accountID,DEMO_USER1);
}
The javadocs for the components
above can be found here.
Given the bank implementation and support classes, the
service
interface, and the service implementation we can proceed with the
generation and deployment of a web service.
How
to Run the Sample
In this sample we will create a
banking web service that wraps and
delegates its functionality to an underlying bank object.
Examining
the How-To Distribution
The How-To zip file should
contain the following:
- ./src/
- contains all Java source code for the
example.
- client/topdown/client
- BankAccountClient.java -
A client
application that invokes the bank service's methods.
- service/topdown/service
- Account.java - A bank account
object.
- AccountException.java
- An exception for
indicating account transgressions
(insufficient funds, maximum withdrawl exceeded, etc.).
- Bank.java -
The interface exposed by the bank object wrapped by the service
implementation.
- BankFactory.java
- A factory that
instantiates objects that implement
the Bank interface.
- BankMemDB.java - An
in-memory implementation of the Bank interface.
- BankServiceInteface.java
- The interface exposed by
the bank web service.
- ./doc/topdown-how-to.html
- This document.
- ./etc/bank.wsdl-
The WSDL document used to generate the service
- ./etc/logging.properties-
Logger properties file to configure the client side Java logging.
- ./build.xml
- An Ant build file.
- ./ant-oracle.xml -
Used by build.xml to
execute
the various Oracle Application Server tasks required for assembly and
deployment of the web service and client.
- ./ant-oracle.properties
- Properties required for
the proper execution of the sample's build script.
Setting
Up the Application
Please check to make sure that the following properties are configured
correctly in the
ant-oracle.properties
file located in the root of the sample's distribution (NOTE:
Some
of these properties will default to the values of corresponding
environment variables as noted below. If you have these
variables
setup in your environment you may not have to alter the values in the
file). If necessary, modify these variable to the proper
values
for you environment:
- oracle.home
- the root
directory of oracle installation. Defaults to ORACLE_HOME
environment
variable.
- java.home
-
the
root directory of JDK installation. Defaults to JAVA_HOME
environment
variable.
- oracleas.host
- the hostname
of the platform on which the OC4J instance is running.
Defaults
to localhost.
- oracleas.http.port
- the port
on which the OC4J HTTP listener is listening. Defaults to 8888.
- oracleas.admin.port
-
the port on which the OC4J administration processor is
listening.
Defaults to 23791.
- oracleas.admin.user
- the
name of the OC4J administrator. Defaults to "oc4jadmin".
- oracleas.admin.password
- the
password for the OC4J administrator. Defaults to "welcome".
- oracleas.binding.module
- the
name of the HTTP web site to which the deployed application is
bound. Defaults to "default-web-site".
In addition, please make sure that the ant command
associated with the
OC4J ant
distribution is in your execution path (%ORACLE_HOME%/ant/bin).
Running
an OC4J Instance
Start an OC4J 11
g Technology
Preview instance as follows:
Generating,
Compiling and Deploying the
Application
To generate, compile, and
deploy the components of this application simply type the following
command from a command prompt in the root directory of the sample:
Executing the default "all"
target of the supplied build.xml file
performs the following steps:
- Generates the service
endpoint interface file - generate-interface target. The
generation is performed utilizing the genInterface
ant task:
<oracle:genInterface wsdl="./etc/bank.wsdl" output="${src.webservice.dir}" databinding="jaxb20"
packageName="howto.jaxws.topdown.service.generated"
valueTypePackagePrefix="howto.jaxws.topdown.service.generated.types"> <classpath>
<pathelement
location="${ORACLE_HOME}/webservices/lib/wsa.jar"/> </classpath> </oracle:genInterface>
The key point here, is the use of the databinding="jaxb20"
parameters that indicates to Oracle WSA tools to use JAXB as
binding mechanism and in the same time use JAX-WS annotations to define
the service. In addition you can organize your code in different
packages to control the location of the generated sources.
- Compiles all the web
service
source files (bank and service
interfaces and implementations) located in src/service
subdirectories - compile-webservice target
- Packages
all the sources in a WAR file to prepare deployment of the application.
One interesting things regarding JavaEE 5 is the fact that you do not
need deployment descriptor to create J2EE module. So to create a WAR
you only need to package all the classes in the WEB-INF/lib
directory of the archive. And during deployment OracleAS will intercept
the various annotations and generates the Web Service and its endpoint
URI.
In this how to the packaging is done using the package-web target.
- Deploys the Web application
module. The .war file generated in
the previous step is now deployed to the server using the ant
deploy
ant task: - deploy-ear target
<oracle:deploy deployerUri="${oracleas.deployer.uri}"
userId="${oracleas.admin.user}"
password="${oracleas.admin.password}"
file="${out.dir}/${web.name}.war"
deploymentName="${app.name}"
logFile="${log.dir}/deploy-ear.log"
bindAllWebApps="${oracleas.binding.module}" />
In this task, the application is deployed and also binded to the OC4J web site. (see bindAllWebApps parameter)
- Creates the client proxy
source files. Once the service is
deployed and available the client proxy can be generated from the
available WSDL file (you can actually view the generated WSDL file of
the service by pointing a browser to the location http://<OC4J
host>:<OC4J http
port>/how-to-jaxws-topdown/BankService?wsdl).
The genProxy
ant task is utilized for this
purpose:
<oracle:genProxy wsdl="http://${oracleas.host}:${oracleas.http.port}/${app.name}/${endpoint.name}?wsdl"
output="${src.cli.dir}"
packageName="howto.jaxws.topdown.client.proxy"
valueTypePackagePrefix="howto.jaxws.topdown.client.proxy.types"
dataBinding="jaxb20"
>
<classpath>
<pathelement path="${bld.cli.dir}"/>
<pathelement location="${OC4J_HOME}/webservices/lib/wsa.jar"/>
</classpath>
</oracle:genProxy >
Once again by using
dataBinding="jaxb20" WSA knows that it must generated JAXB types and create a JAX-WS based service. - Compiles the client
application and associated proxy files.
The proxy files generated in the previous step and the client
application utilizing the proxy for remote communication are compiled
and placed in the ./build/how-to-jaxws-topdown/how-to-jaxws-topdown-client
directory.
Running
the Application
Now that the web service is
deployed and the client is generated and
compiled we are ready to execute the application. From a
command
prompt in the root directory of the sample simply type:
The
generated output from the "run" target should be as follows:
run:
[java] Created an account
for DemoUser1 with
$2000.50
[java]
AccountID for DemoUser is
138.2.8.242_DemoUser1
[java] Depositing $500.50
into account.
[java] Current balance is
now 2501.0
[java] Withdrawing $250.00
from account
[java] Attempting to create
an account for
DemoUser2 with no initial funds
[java] Could not
createAccount for DemoUser2
[java] Insufficient funds
for new
account. You must start an account with more than $0
[java] Created an account
for DemoUser3 with
$3000.00
[java] Attempting to
withdraw 2500.00 from the
account. The account cap is 2000.00
[java] Unable to withdraw
funds.
[java] Exceeded maximum
withdrawal of $2000.00
BUILD SUCCESSFUL
Summary
This how-to provided a detailed
look at how to generate a web service from a WSDL document.
We have seen that the process involves:
- Creating a WSDL document
that reflects the types, messages, and
operations you wish to expose.
- Creating
a service interface
from the WSDL that exposes the
desired
functionality.
- Creating an implementation
of the service interface that utilizes
an instance of the existing class to service requests.
- Compiling and Pacakging the Web Application
- Deploying
the service
application module to the Oracle
Application Server.
- Generating the client proxy
code based on the service WSDL file.
- Writing a
client application
the leverages the client proxies to
interact with the deployed service.