How-To Develop a Web Service
From a Web Service Description
Language (WSDL) Document
First
Publication: 14-Sept-04
Last
Update: 18-Jan-06
Author: Jon Maron
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.
- 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.
- 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?
- OracleAS 10.1.3 is
installed, available from OTN
- Sun's JDK 1.4.2_03 or above,
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) throws AccountException;
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.
Instances of the Bank interface
(e.g. BankMemDB) are created by a BankFactory:
public
class BankFactory {
public BankFactory();
public Bank createBank();
}
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:
- 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">
<documentation>A
service that provides banking operations for client
applications.</documentation>
<operation
name="createAccount">
<documentation>Creates a banking
account.</documentation>
<input message="tns:BankService_createAccount"/>
<output message="tns:BankService_createAccountResponse"/>
<fault name="AccountException"
message="tns:AccountException"/>
</operation>
<operation
name="deposit">
<documentation>Performs a bank
deposit.</documentation>
<input message="tns:BankService_deposit"/>
<output message="tns:BankService_depositResponse"/>
<fault name="AccountException"
message="tns:AccountException"/>
</operation>
<operation
name="getAccountID">
<documentation>Retrieves an account
ID.</documentation>
<input message="tns:BankService_getAccountID"/>
<output message="tns:BankService_getAccountIDResponse"/>
<fault name="AccountException"
message="tns:AccountException"/>
</operation>
<operation
name="getBalance">
<documentation>Retrieves an account
balance.</documentation>
<input message="tns:BankService_getBalance"/>
<output message="tns:BankService_getBalanceResponse"/>
<fault name="AccountException"
message="tns:AccountException"/>
</operation>
<operation
name="withdraw">
<documentation>Withdraws funds from a bank
account.</documentation>
<input message="tns:BankService_withdraw"/>
<output message="tns:BankService_withdrawResponse"/>
<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:
interface
BankService extends java.rmi.Remote{
String
createAccount(String acctName,float initBalance) throws RemoteException,
AccountException;
void
deposit(String acctID, float amount) throws
RemoteException, AccountException;
void
withdraw(String acctID, float amount) throws
RemoteException, AccountException;
float
getBalance(String acctID, String acctName)
throws RemoteException, AccountException;
String
getAccountID(String acctName) throws
RemoteException, AccountException;
}
You
will note that this service
interface extends the java.rmi.Remote interface and that all methods
throw (at least) the java.rmi.RemoteException exception, a requirement
of the JAX-RPC specification.
- Create a service
implementation. The implementation class
(BankServiceImpl) implements the service interface (BankService) and
delegates
various calls to a Bank instance it creates. For example, the
createAccount() method simply calls the underlying Bank's
addNewAccount() method (m_bank is an instance of BankMemDB obtained
from the BankFactory):
public String createAccount(String
acctName,float
initBalance) throws RemoteException,AccountException {
return
m_bank.addNewAccount(acctName,initBalance);
}
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:
- topdown/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/bank.wsdl
-
The WSDL document for the bank service.
- 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.
- topdown/topdown-how-to.html
- This document.
- topdown/topdown-wsdl.html
- This HTML
representation of the WSDL document for this example.
- topdown/build.xml
- An Ant build file.
- topdown/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.
- botomup/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).
Configuring the Environment for a Managed OracleAS Instance
If you are running a managed version of the Oracle Application Server 10
g, you are using OPMN, you must change the following values to match your configuration:
- oracleas.http.port
- the port
on which the Oracle HTTP Server (OHS) is listening.
- oracleas.admin.port
- The OPMN request port, as specified in opmn.xml, the default value is
6003. You can also check the OPMN request port using the
following command: %ORACLE_HOME%/opmn/bin/opmnctl status -port
- oracleas.admin.user
- the
name of the OC4J administrator. Defaults to "oc4jadmin".
- oracleas.deployer.uri
- the URI to use to do the different administration operation
(deployment, undeployment). The file contains different URI depending
of the topology of your application: stand alone OC4J, Managed Single
Node or Managed Cluster. You just need to un-comment the URI that
matches your toplogy.
- oracleas.oc4j.instance - This is the managed OC4J instance where the application will be deployed or undeployed.
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 OracleAS 10
g (10.1.3) instance as follows:
-
Stand Alone Installation: %ORACLE_HOME%/bin/oc4j
start
Note that the oc4j command
expects the JAVA_HOME environment variable to point to a full JDK
installation.
-
OracleAS Managed Installation: %ORACLE_HOME%/opmn/bin/opmnctl startall
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. The
generation is performed utilizing the genInterface
ant task:
<oracle:genInterface
wsdl="${src.webservice.dir}/bank.wsdl"
output="${src.webservice.dir}"
packageName="topdown.service">
<classpath>
<pathelement
location="${ORACLE_HOME}/webservices/lib/wsa.jar"/>
</classpath>
</oracle:genInterface>
- Compiles all the web service
source files (bank and service
interfaces and implementations) located in src/service
subdirectories.
- Creates the service
artifacts (JAX-RPC mapping
file, etc.) required for deployment. The generation of these
files is performed utilizing the topDownAssemble
ant task:
<oracle:topDownAssemble
appName="${app.name}"
wsdl="${src.webservice.dir}/bank.wsdl"
className="topdown.service.BankServiceImpl"
input="${bld.webservice.dir}"
output="${out.dir}"
ear="${lib.dir}/${ear.name}.ear">
<classpath>
<pathelement
location="${ORACLE_HOME}/webservices/lib/wsa.jar"/>
</classpath>
</oracle:topDownAssemble>
The
output of this task is a J2EE
application module (ear file) that contains the various files required
for the deployment of the bank service.
- Deploys the application
module. The .ear file generated in
the previous step is now deployed to the server using the ant
deploy
ant task:
<oracle:deploy
moduleType="ear"
host="${oc4j.host}"
port="${oc4j.admin.port}"
userId="${oc4j.admin.user}"
password="${oc4j.admin.password}"
file="${lib.dir}/${app.name}.ear"
deploymentName="${app.name}"
logFile="deploy-ear.log"/>
In addition, in order for
the service to be accessible via the Oracle Application Server's web
tier it must be bound to an web site:
<oracle:bindWebApp
deploymentName="${app.name}"
host="${oc4j.host}"
port="${oc4j.admin.port}"
userId="${oc4j.admin.user}"
password="${oc4j.admin.password}"
webModule="${web.name}"
webSiteName="${oc4j.binding.module}"
contextRoot="/${app.name}"/>
- 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>/bank/bank?wsdl).
The genProxy
ant task is utilized for this
purpose:
<oracle:genProxy
wsdl="http://${oc4j.host}:${oc4j.http.port}/${app.name}/${app.name}?wsdl"
output="${src.cli.dir}"
packageName="topdown.client.proxy"
>
<classpath>
<pathelement path="${bld.cli.dir}"/>
<pathelement
location="${ORACLE_HOME}/webservices/lib/wsa.jar"/>
</classpath>
</oracle:genProxy>
- 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/bank/bank-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:
This command executes the
client application that performs the
following operations and message exchanges (the messge exchange figures
below were captured by the JDeveloper 10g TCP Packet Monitor and are
provided to illustrate the underlying message exchanges performed
during the execution of the remote service invocations):
- Creates an
account for
DemoUser1 with an initial balance of
$2000.50:
- Deposits an additional
amount of
$500.50 into the account for
DemoUser1:
- Queries for the current
balance:
- Withdraws $250.00 from the
account of DemoUser1:
- Attempts to create an
account
with $0.00 for DemoUser2.
This attempt fails since the amount of funds is instufficient:
- Creates
an account for DemoUser3 with
an initial balance of
$3000.00:
- Attempts to withdraw
$2500.00 from the account of
DemoUser3. This attempt fails since the withdrawl amount
exceeds
the maximum allowed of $2000.00:
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.
- Generating and assembling
the required service artifacts (type
classes, serializers, deserializers, etc.).
- 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.