Friday, August 12, 2005

Case for J2EE Web Services with JBoss(Part 1)

Importance of Web Services


Web Services have an important role to play in an enterprise. They can act as the glue between disparate systems, many of them written in different languages. The basis of integration can be:
a) Through files written on a common shared server.
b) Through a database that is shared between systems.
c) Sending messages between systems.
d) Using a common format(XML) on an universally accepted transport protocol (HTTP).

Section d) is what constitutes the world of Web Services.

The key terms to note in Web Services are as follows:
a) SOAP: It is the protocol used to exchange standard xml messages.
b) WSDL: This is the universally accepted contract for web services. Basically describes the service, the operations it supports, the message formats and the data types.
c) UDDI: It is a registry that acts as a yellow page for service discovery.
d) Marshalling: Convert Java Objects to XML Messages.
e) UnMarshalling: Convert XML Messages to Java Objects.

J2EE Web Services


Now given the basic stuff, lets move on to the J2EE paradigm of developing Web Services. The J2EE world defines a mechanism to use objects, classes and xml deployment descriptors to define web services.

Now lets discuss developing J2EE Web Services with JBoss, a robust J2EE Application Server which provides J2EE 1.4 compliant Web Services since v4.0.x

As a service provider, you will either want to expose a POJO or a SLSB. Now, these two are implementation classes, right? that contain the Web Service. Given this, the client (whether remote or local to the J2EE container) needs to see an INTERFACE.

J2EE 1.4 Web Service aptly defines an interface called SERVICE ENDPOINT INTERFACE (SEI) which basically extends java.rmi.Remote and all its methods have to throw RemoteException in addition to application exceptions. Now this SEI should front the Web Service implementation. J2EE 1.4 defines a deployment archive for the webservices - POJO is deployed as a war (Web Archive) and a SLSB is deployed as a EJB jar.

JBoss and J2EE Web Services



JBoss 4.x supports J2EE 1.4 Web Services. It supports rpc/literal and document/literal style web services only in line with WS-I Basic Profile 1.0 compliance.

Now lets take a very simple example of rpc based web services.

Lets say I have the following Web Service implementation as a POJO:

public class MyTestService
{
public void print(String name)
{
System.out.println("Hello "+name);
}
}

Okay, this is a simple example. Now this is our implementation class. If you want to deploy this as a J2EE Web Service, u will need to define a SEI. It is shown below:

public interface MyInterface extends Remote
{
public void print(String name) throws RemoteException;
}


Serverside Deployment


Now use wscompile tool from Sun's JWSDP and generate the serverside artifacts using this SEI. The artifacts will be a WSDL file, a jaxrpc mapping file. Ignore all the other stuff like serializers, stuns, ties etc. If there are any user defined types that are used in the SEI (remember this was just a simple example), then you need those.

Lets look at the wscompile configuration file(MyService-config.xml) for the serverside generation.

<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
<service name="MyService"
targetnamespace="http://jboss.org/test"
typenamespace="http://jboss.org/test/types"
packagename="org.jboss.test">
<interface name="org.jboss.test.MyInterface ">
</interface>
</service>
</configuration>


The command line usage of wscompile is shown below:

wscompile.bat -cp classes -d build -gen:server -keep -mapping
mapping/jaxrpc-mapping.xml config/MyService-config.xml


Plus, you will need to hand edit a webservices.xml file that lists the SEI and a servlet-link. Plus create a web.xml file, just the way you do web programming. The servlet name will be the same as the servlet-link in the webservices.xml file. Also the servlet class will be the webservices implementation class, in our case, a POJO or MyTestService.

Serverside Packaging
Now package the war file with the implementation class, the SEI class, weservices.xml file, web.xml file and the WSDL file (Remember that it has to be RPC/Literal in our case. If it is RPC/Encoded, u might as well take a hike).
Thats it- that constitutes the serverside deployment.

Lets look at how my serverside deployment archive looks like (as we are exposing a POJO, we will be using a war file).

$ jar tvf MyTest.war
0 Tue Aug 16 01:51:08 CDT 2005 META-INF/
106 Tue Aug 16 01:51:06 CDT 2005 META-INF/MANIFEST.MF
0 Tue Aug 16 01:51:08 CDT 2005 WEB-INF/
0 Tue Aug 16 01:51:08 CDT 2005 WEB-INF/classes/
0 Tue Aug 16 01:10:36 CDT 2005 WEB-INF/classes/org/
0 Tue Aug 16 01:10:36 CDT 2005 WEB-INF/classes/org/jboss/
0 Tue Aug 16 01:50:58 CDT 2005 WEB-INF/classes/org/jboss/test/
237 Tue Aug 16 01:31:20 CDT 2005 WEB-INF/classes/org/jboss/test/MyServiceInterface.class
405 Tue Aug 16 01:51:02 CDT 2005 WEB-INF/classes/org/jboss/test/MyTestService.class
1734 Mon Aug 15 23:15:42 CDT 2005 WEB-INF/jaxrpc-mapping.xml
857 Tue Aug 16 01:29:12 CDT 2005 WEB-INF/webservices.xml
0 Mon Aug 15 23:12:10 CDT 2005 WEB-INF/wsdl/
1378 Mon Aug 15 23:12:10 CDT 2005 WEB-INF/wsdl/MyService.wsdl
393 Tue Aug 16 01:42:48 CDT 2005 WEB-INF/web.xml


Please make a good note of the file structure. Also note that MyTestService.class is my implementation of the service endpoint interface.

Want to have a look at the webservices.xml file, I hand edited?


<?xml version="1.0" encoding="UTF-8" ?>
<webservices xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:impl="http://org.jbos
ss.test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocatio
n="http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_web_s
ervices_1_1.xsd" version="1.1">
<webservice-description>
<webservice-description-name>MyTestService</webservice-description-name>
<wsdl-file>WEB-INF/wsdl/MyService.wsdl</wsdl-file>
<jaxrpc-mapping-file>WEB-INF/jaxrpc-mapping.xml</jaxrpc-mapping-file>
<port-component>
<port-component-name>MyInterfacePort</port-component-name>
<wsdl-port>impl:MyInterfacePort</wsdl-port>
<service-endpoint-interface>org.jboss.test.MyInterface</service-endpoint-interface>
<service-impl-bean>
<servlet-link>MyServlet</servlet-link>
</service-impl-bean>
</port-component>
</webservice-description>
</webservices>


Client Side Deployment

Still in Jboss deploy directory
We are not done yet. We need to do a client side deployment on JBoss so that javax.xml.rpc.Service object gets bound to JNDI for use by clients using the Dynamic Proxy approach. For more information on Dynamic Proxy introduced since JDK 1.3, I suggest looking for documentation on java.lang.reflect.Proxy and the invocation handlers.

With JBoss, client can either use Dynamic Proxy approach or the Dynamic Invocation Interface (DII) approach. Dynamic Proxy approach is recommended for the most compatibility. If you need to use Generated stubs/ties, sorry, using the wrong application server. Read the paragraph carefully. Only Dynamic Proxy or DII. Lets stick to Dynamic Proxy approach.

Client Side Deployment

Now when u deploy the serverside deployment on JBoss, the wsdl will be published to the context provided by the war. In our example,
http://localhost:8080/MyTest?wsdl that will give you the wsdl. Note that I would have packaged my was as MyTest.war

Now use wscompile to do client side generation using the wsdl location I just mentioned or having a physical copy of the wsdl on disk. Now out of the generated artifacts, choose:
a) Generated Service class.
b) SEI class.
c) Generated User types.
d) wsdl file
e) j2ee client file called application-client.xml that contains the service-ref element.

Lets look at the client side configuration file(MyServiceClient-config.xml) for wscompile:


<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
<wsdl location="build/MyService.wsdl" packagename="org.jboss.test">
</wsdl>
</configuration>


Lets look at the command line usage of wscompile to generate client side artifacts

wscompile.bat -cp classes -d build -gen:client -keep -mapping
mapping/jaxrpc-client-mapping.xml config/MyServiceClient-config.xml


Package this together as a jar file and deploy on JBoss. This will bind a Service Proxy to JNDI to be downloadable by WS clients.

Lets have a look at my client side jar that needs to be deployed in the deploy directory of JBoss.


$ jar tvf MyTest-client.jar
0 Tue Aug 16 01:51:08 CDT 2005 META-INF/
106 Tue Aug 16 01:51:06 CDT 2005 META-INF/MANIFEST.MF
797 Tue Aug 16 01:19:58 CDT 2005 META-INF/application-client.xml
242 Tue Aug 16 01:19:58 CDT 2005 META-INF/jboss-client.xml
1602 Tue Aug 16 01:19:58 CDT 2005 META-INF/jaxrpc-client-mapping.xml
0 Tue Aug 16 01:37:48 CDT 2005 META-INF/wsdl/
1382 Tue Aug 16 01:37:48 CDT 2005 META-INF/wsdl/MyService.wsdl
0 Tue Aug 16 01:51:08 CDT 2005 My.jar


Note My.jar contains the Service class wscompile generates. I have attached the structure below:

$ jar tvf My.jar
0 Tue Aug 16 01:51:08 CDT 2005 org/
106 Tue Aug 16 01:51:06 CDT 2005 org/jboss
797 Tue Aug 16 01:19:58 CDT 2005 org/jboss/test
242 Tue Aug 16 01:19:58 CDT 2005 org/jboss/test/MyServiceInterface.class
1602 Tue Aug 16 01:19:58 CDT 2005 org/jboss/test/MyService.class

As shown you will still need to package the service endpoint interface along with the Service interface generated by wscompile.

Now let me show you application-client.xml that is packaged.


<?xml version="1.0" encoding="UTF-8"?>
<application-client xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns
/j2ee/application-client_1_4.xsd"
version="1.4">
<display-name>webservice client</display-name>
<service-ref>
<service-ref-name>service/MyService</service-ref-name>
<service-interface>javax.xml.rpc.Service</service-interface>
<wsdl-file>META-INF/wsdl/MyService.wsdl</wsdl-file>
<jaxrpc-mapping-file>META-INF/jaxrpc-client-mapping.xml</jaxrpc-mapping-file>
<port-component-ref>
<service-endpoint-interface>org.jboss.test.MyInterface</service-endpoint-interface>
</port-component-ref>
</service-ref>
</application-client>


Lets look at the jboss-client.xml that is packaged.


<?xml version='1.0' encoding='UTF-8' ?>

<!DOCTYPE jboss-client PUBLIC
"-//JBoss//DTD Application Client 3.2//EN"
"http://www.jboss.org/j2ee/dtd/jboss-client_3_2.dtd">

<jboss-client>
<jndi-name>ws-client</jndi-name>
</jboss-client>


Note jboss-client.xml is very important as it identifies the client context under which the JNDI bindings will happen.

Web Services Consumers




import javax.xml.namespace.*;
import java.net.URL;
import javax.xml.rpc.*;
import javax.naming.*;
import java.util.*;

import org.jboss.test.*;

public class TestClient
{
private static final String WSDL_LOCATION = "http://localhost:8080/MyTest?wsdl"
;
private static String NAMESPACE = "http://jboss.org/test";
private static final QName SERVICE_NAME = new QName(NAMESPACE, "MyService");
private static String NS_XSD = "http://www.w3.org/2001/XMLSchema";

public static void main(String[] args) throws Exception
{
InitialContext iniCtx = getInitialContext();
Service service = (Service)iniCtx.lookup("java:comp/env/service/MyService");
MyServiceInterface endpoint =
(MyServiceInterface)service.getPort(MyServiceInterface.class);
//Invoke the Web Service
endpoint.print();
}

/**
* Get the client's env context
*/
protected static InitialContext getInitialContext() throws NamingException
{
Properties env = new Properties();
env.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
env.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming.client");
env.setProperty(Context.PROVIDER_URL, "jnp://localhost:1099");
env.setProperty("j2ee.clientName", "ws-client");
return new InitialContext(env);
}


Notice the j2ee.clientName that uses the value from the jboss-client.xml file that was packaged in the client deployment jar. As you can see the test client code uses Dynamic Proxy approach.


Thats it for rpc literal webservices. For more information, please refer to the JBossWS Wiki page in the resources section. Also there are a lot of testcases/samples in the testsuite module of the JBoss CVS source repostitory.


Resources:
XML Data Binding Resources
Accessing Web Services from J2EE Components
JBoss Web Services

General Articles on the Web
Consuming a webservice using JAX-RPC
Web Services with JAX-RPC
Apache Axis 1.2 User Guide
The Argument Against SOAP Encoding

Frequent Questions:
- How Do I get the ServletRequest and associated certificated from a WS Invocation
in JBoss Web Services?



MessageContext messageContext = this.ctx.getMessageContext();
HttpServletRequest request = (HttpServletRequest) messageContext.getProperty("transport.http.servletRequest");
X509Certificate[] certificates = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");

3 comments:

Rici said...

I have tried this to a dot, and I am still getting client not bound when I am running the sample code.

Rici said...

I am still getting client unbound exception when I run your sample code.

11:34:04,687 INFO [ClientDeployer] Client ENC bound under: webservice client
11:34:04,734 INFO [TomcatDeployer] deploy, ctxPath=/testws, warUrl=.../tmp/deploy/tmp8230testws-exp.war/
11:34:04,843 INFO [AxisService] WSDD published to: D:\jboss\server\default\data\wsdl\testws.war\MyInterfacePort.wsdd
11:34:04,843 INFO [WSDLFilePublisher] WSDL published to: file:/D:/jboss/server/default/data/wsdl/testws.war/MyService.wsdl
11:34:04,843 INFO [ServiceDeployer] Web Service deployed: http://wallaby:8080/testws/MyServlet

Rici said...

I am still getting client unbound exception when I run your sample code.

11:34:04,687 INFO [ClientDeployer] Client ENC bound under: webservice client
11:34:04,734 INFO [TomcatDeployer] deploy, ctxPath=/testws, warUrl=.../tmp/deploy/tmp8230testws-exp.war/
11:34:04,843 INFO [AxisService] WSDD published to: D:\jboss\server\default\data\wsdl\testws.war\MyInterfacePort.wsdd
11:34:04,843 INFO [WSDLFilePublisher] WSDL published to: file:/D:/jboss/server/default/data/wsdl/testws.war/MyService.wsdl
11:34:04,843 INFO [ServiceDeployer] Web Service deployed: http://wallaby:8080/testws/MyServlet