Wednesday, November 14, 2007

Changing the endpoint location for a JAX-WS SE Client

Some things JAX-WS makes easy; but other things are not so obvious. One of the quite common issues that confuses developer new to this area is how you change the endpoint location for a web service proxy/client without having to regenerate/recompile the code.

It turns out you have to make use of one of those maps I enumerated in my previous entry and your code needs to looks something like this:

 1  import java.net.URI;

 2  import java.net.URL;
 3  
 4  import java.util.Map;

 5  
 6  import javax.xml.ws.BindingProvider;
 7  import javax.xml.ws.WebServiceRef;

 8  
 9  import project2.proxy.Hello;
10  import project2.proxy.HelloService;
16  

17  public class HelloPortClient
18  {
19    @WebServiceRef
20    private static HelloService helloService;

21  
22    public static void main(String [] args)

23    {
24      helloService = new HelloService();
25      Hello hello = helloService.getHelloPort();

26      setEndpointAddress(hello, "http://some.new.addr/endpoint");
27   
28      hello.sayHello("Bob");

29    }
30   
31   
32   
33    public static void setEndpointAddress(Object port, String newAddress) {

34        assert port instanceof BindingProvider : "Doesn't appear to be a valid port";
35        assert newAddress !=null :"Doesn't appear to be a valid address";

36   
37        //
38   
39        BindingProvider bp = (BindingProvider)port;

40        Map<String, Object> context = bp.getRequestContext();

41        Object oldAddress = context.get(BindingProvider.ENDPOINT_ADDRESS_PROPERTY);

42        context.put(
43          BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
44          newAddress);

45    }
46  }

Note that I extracted the relevant code into a separate method, this you would probably want to move to some kind of utility class. For some reason there isn't a nice helper method to do this that comes with JAX-WS; but hopefully they will fix this in a future revision.

Update: As noted by Kurt in the comments you will probably need to have a local copy of the WSDL in order for this to work properly. Most tools will have an option to help you do this.

Update 22nd Dec 2008: After considering the problem in the real world a little bit more in the cases where you might have policies defined I now consider it better to alter the WSDL location rather than just the endpoint. This means that any extra policies on the new server are read and processed. You could do this using web.xml or a jax-ws catalog file, both can be updated at deploy time with a deployment plan.

8 comments:

Arun Gupta said...

Also explained at:

http://blogs.sun.com/arungupta/entry/totd_2_change_the_endpoint

See more Web services entries at:

http://blogs.sun.com/arungupta/tags/webservices

Kurt said...

The problem seems to be that the line:

Map context = bp.getRequestContext();

throws the following exception if the original WSDL location that was specified when creating the proxy is no longer available:

Caused by: Failed to read wsdl file at: "http://pocapp1:7777/orabpel/default/TaskQueryServiceAuthenticate/1.0?wsdl", caused by: java.net.ConnectException. : Connection refused: connect:

So your example to change the endpoint location is still communicating to the old location before it is switching to the new endpoint. I've verified that if the original WSDL location is still available, that your code does indeed work.

How can we remove all calls to the location that was used during development. E.g., we're promoting code from development to system test and need to change all references to the system test environment.

FYI, here is the proxy file that was generated:

// !DO NOT EDIT THIS FILE!
// This source file is generated by Oracle tools
// Contents may be subject to change
// For reporting problems, use the following
// Version = Oracle WebServices (11.1.1.0.0, build 070830.2200.29857)

@WebServiceClient(wsdlLocation="http://pocapp1:7777/orabpel/default/TaskQueryServiceAuthenticate/1.0?wsdl",
targetNamespace="http://xmlns.oracle.com/TaskQueryServiceAuthenticate",
name="TaskQueryServiceAuthenticate")
public class TaskQueryServiceAuthenticate_Service
extends Service
{
private static URL wsdlLocationURL;
static {
try
{
wsdlLocationURL =
new URL("http://pocapp1:7777/orabpel/default/TaskQueryServiceAuthenticate/1.0?wsdl");
}
catch (MalformedURLException e)
{
e.printStackTrace();
}
}

public TaskQueryServiceAuthenticate_Service()
{
super(wsdlLocationURL,
new QName("http://xmlns.oracle.com/TaskQueryServiceAuthenticate",
"TaskQueryServiceAuthenticate"));
}

public TaskQueryServiceAuthenticate_Service(URL wsdlLocation,
QName serviceName)
{
super(wsdlLocation, serviceName);
}

@WebEndpoint(name="TaskQueryServiceAuthenticatePort")
public TaskQueryServiceAuthenticate getTaskQueryServiceAuthenticatePort()
{
return (TaskQueryServiceAuthenticate) super.getPort(new QName("http://xmlns.oracle.com/TaskQueryServiceAuthenticate",
"TaskQueryServiceAuthenticatePort"),
TaskQueryServiceAuthenticate.class);
}
}

Gerard Davison said...

Thanks for your comment Kurt,

I did indeed miss an important point in this blog entry. If you will not have access to the original WSDL, or indeed just want a local copy for performance reasons, it is important you make a local copy of the WSDL document.

If you are using the wizards in JDeveloper, which judging by the output you are, you should have been offered the chance when you first generated the proxy to make a local copy of the WSDL. This would have created copy on your source path and put the right entries in the Service class.

You might find that the build of JDeveloper you have doesn't update the WSDL location correctly. If so I can only apologize and suggest you refere to this link which should give you enough info to massage the results. This of course will be fixed in a later public build of JDeveloper.

Hope this is enough information to get you going, if not get back to me and I will try to create a more explicit example.

Thanks,

Gerard

Kurt said...

I have since tried clicking the "copy WSDL into project" checkbox on proxy creation. It does indeed copy the WSDL into the project, but it still generates the code pointing at the remote URL WSDL and not the local copy of the WSDL, so at runtime I'm running into the same issue. Maybe this has been changed/fixed in a post 4684 build?

Gerard Davison said...

Kurt,

Sorry about that, I have raised a clutch of bugs on this wizard today to deal with a few issues that your post has raised. Hopefully these can be resolved in time for the next public build.

I did notice on the build you are using that the WSDL is copied into the public_html directory which is of little use in the JSE case. You need to move this to the same directory as you service class and then modify the class to look something like:


@WebServiceClient(wsdlLocation="TaskQueryService.wsdl",
targetNamespace="http://xmlns.oracle.com/TaskQueryServiceAuthenticate",
name="TaskQueryServiceAuthenticate")
public class TaskQueryServiceAuthenticate_Service
extends Service
{
private static URL wsdlLocationURL;

static {

wsdlLocationURL =
TaskQueryServiceAuthenticate_Service.class.getResource("TaskQueryService.wsdl");
}

....
}


Hopefully this is enough to get your going,

Gerard

Eric said...

Hi Gerard,
This is a great example.
Do you have another example that sending a SOAP message with WS-Addressing headers for a JAX-WS SE client? I have looked for such a JDeveloper example for a while, but I got nothing. Appreciate if you will help.

Eric

ber said...

Thanks a lot for the explanations Gerard. Jax-ws is certainly amazingly powerful but this most needed details are not easy to undig.

venu said...

Hi Gerard,
I wanted to edit the EndPoint but was totally unaware of the way to do that. Too much of abstraction in JAX-WS makes few things too complex, as u mentioned. Thanks for your nice post.

Thanks
Venu