Friday, August 31, 2007

Building an async JAX-WS from the bottom up.

Now and then in JDeveloper land we have an organization wide bug hunt to improve the overall quality of our product. For the most recent one I decided to try to write a async web service from the bottom up.

For the purposes of this experiment I am going to create a simple HelloWorld example so first off lets created the primary interface:

package model;

import ...;

@WebService
public class Hello
{

  public void sayHello(String name)
  {
  ...
  }
}

And of course we need to define the interface for the callback service:

package model;

@WebService
public interface HelloCallback
{
  public void response(String message);
}

Simple enough, right? Now I we are going to create a static wsdl under public_html/wsdl. Use the normal wizard to create a blank document then cut and paste the results of "Show WSDL for annotations" from the Hello class. You also need to do something similar for the HelloCallback service. Unfortunately you can't show the WSDL for a SEI, although I have raised that bug, so the quickest way to generate the WSDL content is to temporarily convert HelloCallback to a class.

To complete the WSDL add a partnerLinkType, this is used by tools such as BPEL to link the two halves of the equation:

    <partnerLinkType name="HelloService"
                     xmlns="http://schemas.xmlsoap.org/ws/2003/05/partner-link/">
        <role name="In">
            <portType name="tns:Hello"/>
        </role>
        <role name="Out">
            <portType name="tns:HelloCallBack"/>
        </role>
    </partnerLinkType>

Then it is worth updating the Hello class to reference this WSDL in the annotations:

package model;

@WebService(wsdlLocation = "/wsdl/Hello.wsdl")
public class Hello 
{
  ...
}

Okay so the specifications are in place lets look at a trivial implementation of the sayHello method. Note that in this case the response can arrive before the original call has completed; but this will do for this example.

SOAPMessageContext smc = (SOAPMessageContext)_context.getMessageContext();
        
// Get hold of the AddressingProperties and look up the reply to address
// and later the return message id
//
        
AddressingProperties ap = AddressingUtils.getAddressingProperties(smc.getMessage());
EndpointReference endpointReference = ap.getReplyTo();
AttributedURI address = endpointReference.getAddress();
URI replyTo = address.getURI();
        
// 

try
{

  // Use the servlet context to get a reference to the local wsdl
  //  

  ServletContext sc = (ServletContext)_context.getMessageContext().get(
    "javax.xml.ws.servlet.context");
  URL found = sc.getResource("/wsdl/Hello.wsdl");
 
  // Create a new service instance on the callback

  Service s = Service.create(found, new QName(
                             "http://model/",
                             "HelloCallBackService"));
            
  // Create a port

  HelloCallBack acb = s.getPort(
           new QName(
               "http://model/",
               "HelloCallBackPort"),
               HelloCallBack.class); 
            
  BindingProvider bp = (BindingProvider)acb;
  Map requestProperties = bp.getRequestContext();

  // Change the endpoint address to the match the replyTo address
            
  requestProperties.put(
    BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
    replyTo.toString());

  // Make sure that this request has the correct addressing headers
            
  AddressingUtils.setupClientSideWSAddressing(requestProperties);
  String t = requestProperties.keySet().toString();
  AddressingProperties rap = 
    AddressingUtils.getOutboundAddressingProperties(requestProperties);
  rap.setRelatesTo(
    ap.getMessageID());
       
  // Invoke the response address
  //
     
  acb.response("Hello " + name);
}
catch (Exception ex) {
  ex.printStackTrace();            
}
        

Part of the pleasure of using JAX-WS over JAX-RPC is the simplicity of invoking services dynamically in this way. As you can see above this is actually really quite straight forward.

So how would you go about testing this, well the HTTP Analyzer in JDeveloper doesn't support WS-Addressing headers properly although we are working on this. You can of course create your own headers in the HTTP Content view, it might look like this:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Header xmlns:ns1="http://www.w3.org/2005/08/addressing">
      <ns1:MessageID>ws:uniqueAddress</ns1:MessageID>
      <ns1:ReplyTo>
         <ns1:Address>http://returnAddress.com/example</ns1:Address>
      </ns1:ReplyTo>
   </soap:Header>
   <soap:Body xmlns:ns2="http://model/">
      <ns2:sayHello>
         <arg0>John</arg0>
      </ns2:sayHello>
   </soap:Body>
</soap:Envelope>

If you start up the HTTP Analyzer before the embedded server you can send his message to the endpoint and you should see the failed attempt to send the message to the returnAddress url in the analyzer log. It should look something like:

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing">
   <env:Header>
      <wsa:To>http://returnAddress.com/example</wsa:To>
      <ns1:MessageID>ws:anotherUniqueAddress</ns1:MessageID>
      <wsa:RelatesTo xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" soap:mustUnderstand="0">ws:uniqueAddress</wsa:RelatesTo>
   </env:Header>
   <env:Body>
      <ns0:response xmlns:ns0="http://model/">
         <arg0 xmlns="">Hello John</arg0>
      </ns0:response>
   </env:Body>
</env:Envelope>

This is worth remembering this trick if you are trying to test a async web service and don't want to go to the bother of setting up a valid return address to connect to.

Next time I get a few minutes I will take a look at the client side of this equation. Back to fixing the bugs I have just found.

6 comments:

Void said...

Hi Gerard,

I am in the process of creating an async jax-ws as mentioned in this post. I have created the class Hello and generated the wsdl. Similarly Helllocallback and generated the wsdl.

In the post you have mentioned to create an empty wsdl document and copy paste the generated wsdl.

Do i need to copy the wsdl generated for both the class?

Can you please throw some ligh on this?

Gerard Davison said...

Yup,

You need the definitions from both of these WSDLs integrated into one. For use with BPEL et al it is also good to put the partner links in.

Gerard

Shafter said...

Hi Gerard,

This is the only real example I found on the web with a step by step directions to create an asynchronous web-service in Jdeveloper (THANKS!!).

I am very very new to java web-services and my only real experience has been to create web-services using the JDev wizard. This is a hard requirement for one of our projects and we have to figure a way out.

I am working in 11g (11.1.1.1) and cant seem to get it to work. I am guessing this is probably because I am importing the wrong package and all. Or pasting your code in the wrong section?

Is there a way you can send me a copy of this project (or any async java webservice project) that will work in 11g? It will really save me a lot of time.

Thanks a lot for maintaining this blog!

Gerard Davison said...

Yeah some of this code is oc4j specific. Unfortunately I neither have the original or updated 11 versions of this code sorry.

If you tellme what is going wrong I might be able to suggest a way forward.

Shafter said...

Thanks for your reply. I am not quite sure what I am doing wrong but it compiles fine but when I try running it locally it gives me this error:

com.sun.xml.ws.server.EndpointMessageContextImpl cannot be cast to javax.xml.ws.handler.soap.SOAPMessageContext


On this line:

SOAPMessageContext smc = (SOAPMessageContext)context.getMessageContext();


If you can e-mail me with your e-mail address, I could send you the weblogic project and maybe it will be a quick fix for you. This is very urgent indeed. We are trying to call these long running web-services from BPEL.

Also, is there another way to create an asynchronous Java webservice in Jdeveloper 11g that I am missing?

My e-mail: shafayat.bari@gmail.com

Thanks a lot again!

Alexander Casall said...

Hi,
I have the same problem like Shafter, and i got the same Cast Exception:

com.sun.xml.ws.server.EndpointMessageContextImpl cannot be cast to javax.xml.ws.handler.soap.SOAPMessageContext


On this line:

SOAPMessageContext smc = (SOAPMessageContext)context.getMessageContext();

---
What is the fix? Please write me a Mail,
Thanks!!!