Apache CXF and WS-Discovery

One of the new features in Apache CXF 2.7.x that I worked hard on was the introduction of support for WS-Discovery. WS-Discovery is basically a standard way for a service to announce when it’s available as well as standard way to probe the network for services that meet certain criteria and have the services that meet that criteria provide a response. Most ESB’s now have some sort of registry component or locator component or similar that provide a similar need. However, they are generally more proprietary in nature and, in many cases, will only work with services deployed in or managed by that ESB. WS-Discovery is completely standards based (OASIS) and is completely independent of any ESB, application server, etc…

So, how does it work? If the CXF WS-Discovery jars/bundles are available when a service starts, CXF will automatically register a ServerLifecycleListener onto the Bus. When the service starts, that listener will send a WS-Discovery “HELLO” message out on the network using the SOAP over UDP spec. When the service stops, it will send a “BYE” message out. Most users don’t need those messages, but if you do have an application that needs to keep track of services that are available, you could listen for them. The CXF WS-Discovery listener will also start an internal WS-Discovery service that will listen for SOAP/UDP “PROBE” requests on the network, process those requests to see if the service matches it, and respond with information (such as the address URL) if it does. This is all automatic. All that is needed is to add the WS-Discovery jars.

CXF also provides an API for probing the network for services. It’s only slightly documented right now, but you can easily look at the source for WSDiscoveryClient. Basically, some simple code like:

WSDiscoveryClient client = new WSDiscoveryClient();
List references 
    = client.probe(new QName("http://cxf.apache.org/hello_world/discovery",
                             "Greeter"));
client.close();
        
//loop through all of them and have them greet me.
GreeterService service = new GreeterService();
for (EndpointReference ref : references) {
     Greeter g = service.getPort(ref, Greeter.class);
     System.out.println(g.greetMe("World"));
}

would use the WSDiscoveryClient to probe the network for all the services that can provide the “Greeter” service and then calls off to each one. It’s very simple.

The main problem with the WS-Discovery implementation in CXF 2.7.0 through 2.7.4 was that it only implemented WS-Discovery 1.1 as that is the actual OASIS standard that I looked at. However, there are many devices out there that only will respond to WS-Discovery 1.0 probes. In particular, any of the IP cameras that implement the ONVIF specification will only respond to 1.0. Thus, in 2.7.5, I updated the code to also handle WS-Discovery 1.0. The WSDiscoveryClient object has a setVersion10() method on it to change the probes over to WS-Discovery 1.0. With support for WS-Discovery 1.0, you can now use CXF to probe for any devices on the network that meet the ONVIF standard. No proprietary registry or anything required.

That’s pretty cool.

Now that the WS-Discovery stuff in CXF is fairly well tested and is known to work, I expect more of the downstream consumers of CXF to start integrating it into product offerings. I’m hoping to work on getting the Talend locator updated to use it. However, with the next (5.3.1) version of Talend ESB (due next month), you’ll be able to just “feature:install” the cxf-ws-discovery feature into the ESB and have the above all work. I also see that JBoss has already started integrating it into their application server.

9 thoughts on “Apache CXF and WS-Discovery

  1. Hi Dan, as you write CXF’s new implementation probes for all services matching a given QName [QName(“http://cxf.apache.org/hello_world/discovery”,”Greeter”))] — but what if I don’t know the QName? Is it possible to generically probe for any web services out on the Network or is a QName obligatory in WS-Discovery?

    Also, what is the use case for having multiple web services available sharing the *same* namespace? Is it a redundancy issue–i.e., we can assume any web services sharing a QName will be identical so we only need to call just one (of potentially several services) available? Or is it thought that those several web services sharing the same namespace could provide different results, and hence a use case for calling all of them (as your sample code does) could have a practical application? (Or either?)

    Finally can this “network listening” be done by a SOAP client *outside* the network (i.e., just over the Internet?). Let’s say I have a SOAP client that wants to check whether DoubleIt, TripleIt and/or QuadrupleIt is available from a given server — what is the type of probe sent in that case (or it wouldn’t be, instead just a normal WSDL query would be done?) Thanks!

  2. Glen,

    The WS-Discovery spec does describe a variety of different probe semantics. You can probe by type (QName above), scope (if the deployer has given a service a scope), or “everything”.

    For a use case, the common one I saw with the person testing this was that he had 20+ of the security cameras that implement the ONVIF spec. They all implement the same web service. Thus, you can find all the cameras, iterate through them, etc.. Each camera obviously points at a different location. However, it could also be used as a simple way to load balance where you get a list of 5 identical services and just pick one.

    Since this is soap over a broadcast UDP, it wouldn’t survive outside the network. HOWEVER, WS-Discovery does allow the use of Discovery proxy that may “talk” to another proxy on another network to aggregate responses. CXF doesn’t yet provide a discovery proxy, but the runtime does support one if one is detected.

  3. Dear Dan,

    I am doing my master thesis and I was requested to use WS-Discovery in my project. I already tried to use Java-WS-Discovery, but it is buggy and the implementation is not up-to-date. My supervisor told me I should use this implementation of CXF with WS-Discovery instead. But I’m having problems making it work. And I think you are the right person to ask for help πŸ™‚

    Let me describe what I did to try to use it. I just followed a series of video tutorials that show how to create a Web Service with CXF in Eclipse (https://www.youtube.com/watch?v=UpZgxutMgYg).

    So I have the web service project and the client project generated by Eclipse that work with Tomcat and CXF. Now I want to use CXF-WS-Discovery to actually get the WSDL address dynamically, instead of having it hardcoded. So, according to your explanation, all I need to do is include the 2 .jars, which I did (in both projects).

    Then, I added in my client the following code:

    WSDiscoveryClient client = new WSDiscoveryClient();
    List references = client.probe(new QName(“http://ws/”, “DeviceProxyService”));
    client.close();

    DeviceProxyService service = new DeviceProxyService();
    for (EndpointReference ref : references) {
    DeviceProxySEI dp = service.getPort(ref, DeviceProxySEI.class);
    System.out.println(dp.whatIsTheAnswer(“bebe”));
    }

    But right when the line 2 is going to be executed, it throws this exception:

    May 27, 2013 1:03:52 PM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
    INFO: Creating Service {http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01}DiscoveryProxy from class org.apache.cxf.jaxws.support.DummyImpl
    May 27, 2013 1:03:56 PM org.apache.cxf.phase.PhaseInterceptorChain doDefaultLogging
    WARNING: Interceptor for {http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01}DiscoveryProxy#{http://cxf.apache.org/jaxws/dispatch}Invoke has thrown exception, unwinding now
    org.apache.cxf.interceptor.Fault: Could not send Message.
    at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:64)
    at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:271)
    at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:530)
    at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:456)
    at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:434)
    at org.apache.cxf.endpoint.ClientImpl.invokeWrapped(ClientImpl.java:427)
    at org.apache.cxf.jaxws.DispatchImpl.invokeAsync(DispatchImpl.java:416)
    at org.apache.cxf.ws.discovery.WSDiscoveryClient.probe(WSDiscoveryClient.java:379)
    at org.apache.cxf.ws.discovery.WSDiscoveryClient.probe(WSDiscoveryClient.java:328)
    at ws.DeviceProxySEI_DeviceProxyPort_Client.main(DeviceProxySEI_DeviceProxyPort_Client.java:43)
    Caused by: java.net.SocketTimeoutException: Receive timed out
    at java.net.TwoStacksPlainDatagramSocketImpl.receive0(Native Method)
    at java.net.TwoStacksPlainDatagramSocketImpl.receive(TwoStacksPlainDatagramSocketImpl.java:90)
    at java.net.DatagramSocket.receive(DatagramSocket.java:775)
    at org.apache.cxf.transport.udp.UDPConduit$UDPBroadcastOutputStream.close(UDPConduit.java:289)
    at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56)
    at org.apache.cxf.transport.udp.UDPConduit.close(UDPConduit.java:118)
    at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:62)
    … 9 more

    I don’t know if I’m initializing the wrong QName… I’ve changed it to many different things, but all give this result. Just so you know, the Web Service in the generated code is like this:

    @javax.jws.WebService(
    serviceName = “DeviceProxyService”,
    portName = “DeviceProxyPort”,
    targetNamespace = “http://ws/”,
    wsdlLocation = “http://localhost:8080/DeviceProxy/services/DeviceProxyPort?wsdl”,
    endpointInterface = “ws.DeviceProxySEI”)

    That’s where I got the parameters of the QName from.

    Since there is no more documentation or examples online, I don’t know what to do. Therefore, I ask you, because you sure know how to implement this.

    Sorry for the long post. I hope you can help me.
    Beatriz

  4. Hi Dan,

    After I sent the long post, I changed the Qname to new QName(“http://ws/”, “DeviceProxySEI”) and the probe worked.

    Yet, this line throws exception: System.out.println(dp.whatIsTheAnswer(“lala”)). So I cannot really call the method of that class. Do you have a clue why the probe works and I get the reference but I can’t call the methods from it?

    There’s also something I don’t understand. In the references variable I don’t get the WSDL address. I just get “/DeviceProxyPort” in the address->uri. How do I access the web service of which I don’t know the WSDL, without getting it here? Or having these references, I don’t need to know the WSDL address?

    Thanks in advance,
    Beatriz.

  5. Also, this line: GreeterService service = new GreeterService();
    When Eclipse generates my Web service, it hardcodes the WSDL URL inside the GreeterService class. So, if you don’t pass any parameters in this line, this Service is initialized with that hardcoded WSDL URL. But in reality I won’t have that WSDL URL to initialize that Service. So, I don’t understand where to get the WSDL URL from, or if it’s needed at all in this case.

    Either way, having the hardcoded WSDL URL inside that Service class or not, the code to call the method of the discovered reference doesn’t work for me.

    Do you have any idea what could be wrong? As I said, I just generated the Web Service with Eclipse, added the 2 .jars and your example code. The probe works, I get the 1 reference, but it throws exception when trying to call the method of that reference.

    Please help me if you have a clue why πŸ™
    Thanks in advance.

  6. Beatriz,

    This is likely better sent to users@cxf.apache.org. If you can, also include the stack trace for the failed call. Also, if you can get a wire trace (with wireshark or similar) of the Discover response coming back, that would be great. I’d like to see if the URL in that response is bogus or wrong or something.

  7. hi dan:

    here is the code:

    i generate DiscoveryService.java with cxf wsdl2java
    /**
    * This class was generated by Apache CXF 2.6.1
    * 2013-06-03T10:42:48.096+08:00
    * Generated source version: 2.6.1
    *
    */
    @WebServiceClient(name = “DiscoveryService”,
    wsdlLocation = “d:/onvif/remotediscovery.wsdl”,
    targetNamespace = “http://www.onvif.org/ver10/network/wsdl”)
    public class DiscoveryService extends Service {

    public final static URL WSDL_LOCATION;

    public final static QName SERVICE = new QName(“http://www.onvif.org/ver10/network/wsdl”, “DiscoveryService”);
    public final static QName DiscoveryPort2 = new QName(“http://www.onvif.org/ver10/network/wsdl”, “DiscoveryPort2”);
    public final static QName DiscoveryPort = new QName(“http://www.onvif.org/ver10/network/wsdl”, “DiscoveryPort”);
    static {
    URL url = null;
    try {
    url = new URL(“file:///d|/onvif/remotediscovery.wsdl”);
    } catch (MalformedURLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }

    WSDL_LOCATION = url;
    }
    。。。。。。。。

    in TestClass main() function
    WSDiscoveryClient client = new WSDiscoveryClient();
    List tt = client.probe(new QName(“http://www.onvif.org/ver10/network/wsdl”, “DiscoveryService”));
    client.close();

    i run this code ,but tt.size()=0

    there is a ipc in lan
    counld u help me out, thx in adv

  8. With many of the ONVIF devices, they’ll only respond to a WS_Discover 1.0 probe. You may need to add a call like:

    client.setVersion10();

    prior to the probe call.

  9. hi dan

    i added client.setVersion10(), it work!

    I can not use words to express my gratitude

    πŸ™‚

Leave a Comment

Your email address will not be published. Required fields are marked *