Skip to main content

Dynamic Discovery and Invocation of Web services

How to dynamically discover and invoke a Web service using UDDI and Axis.

Damian Hagge (hagge@uk.ibm.com), Web services development/architecture, IBM, Software Group
Damian Hagge works at IBM's Hursley Development Laboratories. Although he currently lives in the UK, he originated five hours counterclockwise from his present location just above the forty-ninth parallel (that's Canada for those whose geography isn't up to scratch). Aside from working in Web services and writing long articles on obscure subject matter, Damian is a veritable encyclopedia of useless facts; for instance, did you know that a hippopotamus has the greatest per-square-inch jaw pressure of any animal and is the most dangerous animal outside of water? Damian also does some normal stuff like power lifting (if you call that normal!). You can contact Damian Hagge at hagge@uk.ibm.com.

Summary:  In order to take full advantage of the flexibility and power of Web services, the user must be able to dynamically discover and invoke a Web service implementation. This is the ultimate promise of Web services and the original reason why technologies like UDDI were developed. It has even been proposed that businesses could provide publicly-accessible implementations of Web services, which any Joe or Jane could use for buying discount gummy bears, or more practical things, such as insurance. Although, this idea is not yet ready for prime time, there is still use for such dynamic invocation behind the firewall. In this article, Damian Hagge demonstrates how a Web services client can dynamically discover and invoke a Web service without any prior knowledge of its makeup.

Date:  01 Aug 2001
Level:  Introductory
Activity:  2580 views

In this article I will build upon the basic tenets of Web services. If you do not have a good understanding of UDDI, WSDL, and Axis, or if you haven't created a basic Web service before, then you will probably not find this article very useful. If this is the case then I would suggest you start with some resources which explain Web services and the associated technologies. On the other hand, if you do understand Web services and are confused or unsure about any of the material presented in this article please post a comment in the Web Services Technical forum, which I will monitor on a regular basis.

The server implementation

Although this article is not about the server-side implementation of a Web service, it is worth mentioning the makeup of the Web service I will use. I will base my Web service upon a simple Java class with a method that takes two integers as parameters and returns an integer which is the multiplication of the parameters. This class will, of course, be exposed as a Web service, and that service will need to be published to UDDI.

If you are using the WebSphere SDK for Web Services (WSDK) then simply download the server side implementation (see the Resources section), which I've already created for you, and follow these steps:

  1. Unzip the downloaded file into the WSDK's services\applications directory. (that is, services\applications\WebMath).
  2. Run wsdkconfig and install WebMath.
  3. Start up the application server.
  4. Type run publish at the command line from the services\applications\WebMath\client directory.

And that's it! The Web service is published to UDDI and waiting for client requests.

If you don't have the Web Sphere SDK for Web services it can be downloaded for free (see the Resources section). Otherwise you will need to create the above described Web service from scratch on whichever platform you want to use. Just be sure that when it's published to UDDI the business entity is "Damian Hagge Ltd." and that the business service is "WebMathService". If you do this, then the client code provided in the package for this article (see Resources) should work like a charm.


The client

I'm now ready to talk about the interesting part -- dynamic discovery and invocation. The code I will discuss is DynamicInvoke.java and can be found in the WebMath\client directory of the download. Although this code is somewhat lengthy, I will endeavor to explain it as concisely as possible.

Open source API's to invoke Web services

  • uddi4j - query and publish to UDDI
  • wsdl4j - to parse and glean information from WSDL
  • Apache Axis - to encode/decode SOAP messages

So what do I need to do to dynamically discover and invoke the service? Well, first I will need to discover information about the service from UDDI. Second, I will need to read the WSDL implementation file from the service provider and parse it for various information. Finally, I will have enough information to marshal (encode) a SOAP request to the Web service implementation using Axis.

In the client code this is accomplished using the open source packages uddi4j, wsdl4j, and Apache Axis. I will use uddi4j to browse the UDDI registry as it provides an API which allows the user to make inquiries and publish to any UDDI 2.0 registry. The wsdl4j package will be used to programmatically represent the elements of a WSDL file so I can browse and glean various information from it. Then I will use Axis to actually make the SOAP request to the server and wait for the response.

Finding the Web service in UDDI

In order to dynamically invoke your Web service you need to know four things. You must know the inquiry URL of the UDDI registry, the publish URL of the UDDI, the name of the business entity, and the name of the business service. This information can be found in Listing 1.


Listing 1. DynamicInvoke.java - line 38
	
    public class DynamicInvoke {

        private String uddiInquiryURL = "http://localhost:80/uddisoap/inquiryapi";
        private String uddiPublishURL = "http://localhost:80/uddisoap/publishapi";
        private String businessName = "Damian Hagge Ltd.";
        private String serviceName  = "WebMathService";

This says where the UDDI is, which business entity to look for, and once that is found, which business service to select. I use the UDDI URLs to create a proxy to the UDDI registry which I then use to query the registry to find the business service. This is done as explained in Listing 2.


Listing 2. DynamicInvoke.java - line 65
	
    // create a proxy to the UDDI
    UDDIProxy proxy = new UDDIProxy(
                     new URL(uddiInquiryURL), new URL(uddiPublishURL));
    // we need to find the business in the UDDI
    // we must first create the Vector of business name
    Vector names = new Vector();
    names.add(new Name(businessName));
    // now get a list of all business matching our search criteria
    BusinessList businessList = 
           proxy.find_business(names, null, null, null, null, null,10);
    // now we need to find the BusinessInfo object for our business
    Vector businessInfoVector  = 
           businessList.getBusinessInfos().getBusinessInfoVector();
    BusinessInfo businessInfo = null;
    for (int i = 0; i < businessInfoVector.size(); i++) {
       businessInfo = (BusinessInfo)businessInfoVector.elementAt(i);
       // make sure we have the right one
       if(businessName.equals(businessInfo.getNameString())) {
          break;
       }
    }

Here you can see that I created a UDDIProxy. Then I created a Vector containing the desired business name and passed it in as a parameter to UDDIProxy.find_business. This call returned a BusinessList object which contains a BusinessInfos object. BusinessInfos simply contains a vector of BusinessInfo objects. This is a common trait of the uddi4j API where one object has a reference to another object that simply wraps a vector. This programmatic pattern is used throughout the client code so, throughout the rest of this article, I won't explain the semantics of code which simply finds the classes in a vector that is wrapped by a parent object.

I then iterate through all the BusinessInfo objects to find the one which has the name Damian Hagge Ltd.. This ensures that I have the right business entity.

Now I need to find the business service with the name WebMathService. This is done by searching all the BusinessService object's referenced, albeit indirectly, by the BusinessInfo object. When I find the BusinessService with the name WebMathService, I have the right class. The code that does this is in Listing 3.


Listing 3. DynamicInvoke.java - line 88
	
    // now find the service info
    Vector serviceInfoVector  = 
            businessInfo.getServiceInfos().getServiceInfoVector();
    ServiceInfo serviceInfo = null;
    for (int i = 0; i < serviceInfoVector.size(); i++) {
        serviceInfo = (ServiceInfo)serviceInfoVector.elementAt(i);
        // make sure we have the right one
        if(serviceName.equals(serviceInfo.getNameString())) {
           break;
        }
    }
    // we now need to get the business service object for our service
    // we do this by getting the ServiceDetail object first, and
    // getting the BusinessService objects through it
    ServiceDetail serviceDetail = proxy.get_serviceDetail(
                                           serviceInfo.getServiceKey());
    Vector businessServices = serviceDetail.getBusinessServiceVector();
    BusinessService businessService = null;
    for (int i = 0; i < businessServices.size(); i++) {
    businessService = (BusinessService)businessServices.elementAt(i);
        // make sure we have the right one
        if(serviceName.equals(businessService.getDefaultNameString())) {
            break;
        }
    }

Again, you can see the programmatic pattern of one object referencing another object that simply contains a vector of the object(s) I am really looking for.

Now that I have the business service, I need to somehow glean the URI of the WSDL implementation on the service provider's machine. This will be represented by the Overview URL element of the tModel in the UDDI registry. To find this I must first find the business service's binding template which contains an http-based access point. Once I've found this, then I can find the appropriate tModel which is referenced by the binding template. Programmatically, I do this listed in Listing 4.


Listing 4. DynamicInvoke.java - line 115
		
    // ok, now we have the business service so we can get the binding template
    Vector bindingTemplateVector = 
            businessService.getBindingTemplates().getBindingTemplateVector();
    AccessPoint accessPoint = null;
    BindingTemplate bindingTemplate = null;
    for(int i=0; i<bindingTemplateVector.size(); i++) {
        // find the binding template with an http access point
        bindingTemplate = (BindingTemplate)bindingTemplateVector.elementAt(i);
        accessPoint = bindingTemplate.getAccessPoint();
        if(accessPoint.getURLType().equals("http")) {
            break;
        }
    }
    // ok now we know which binding template we're dealing with
    // we can now find out the overview URL
    Vector tmodelInstanceInfoVector = 
        bindingTemplate.getTModelInstanceDetails().getTModelInstanceInfoVector();
    String wsdlImplURI = null;
    for(int i=0; i<tmodelInstanceInfoVector.size(); i++) {
        TModelInstanceInfo instanceInfo = 
                (TModelInstanceInfo)tmodelInstanceInfoVector.elementAt(i);
        InstanceDetails details = instanceInfo.getInstanceDetails();
        OverviewDoc wsdlImpl = details.getOverviewDoc();
        wsdlImplURI = wsdlImpl.getOverviewURLString();
        if(wsdlImplURI != null) break;
    }

In the first code block I looked at all the binding templates referenced by the business service and found the one which had a URL type of "http". I then iterated through all the tModel's references by the binding template and stopped when I found the first overview URL (that is, location of WSDL implementation). Now that I know the URL of the WSDL implementation, I can proceed to use wsdl4j to request the document directly from the service provider and parse it for the parameters I need to pass to Axis in order to invoke the service.

Note: Depending on the implementation of UDDI 2.0-compliant registry you are using, the overview URL contained in the tModel might not point to the WSDL implementation. If this is the case, then you will need to do more investigating as to how you can glean the location of the service provider's WSDL implementation from either UDDI or the location where the overview URL points.

Parsing the WSDL

Now that you have the URL for the Web service's WSDL implementation you can use wsdl4j to parse it. In order to call the service using Axis you will need to glean the following information from the WSDL:

  1. the target namespace
  2. the service name
  3. the port name
  4. the operation name
  5. the operations input parameters

In my example, in order to glean this information, I must first obtain Definition objects for both the WSDL implementation and the WSDL interface. My code is in Listing 5.


Listing 5. DynamicInvoke.java - line 159
	
	// first get the definition object got the WSDL impl
	try {
	    WSDLFactory factory = new WSDLFactoryImpl();
	    WSDLReader reader = factory.newWSDLReader();
	    implDef = reader.readWSDL(implURI);
	} catch(WSDLException e) {
	    e.printStackTrace();
	}
	if(implDef==null) {
        throw new WSDLException(
            WSDLException.OTHER_ERROR,"No WSDL impl definition found.");
	}
	// now get the Definition object for the interface WSDL
	Map imports = implDef.getImports();
	Set s = imports.keySet();
	Iterator it = s.iterator();
	while(it.hasNext()) {
            Object o = it.next();
	    Vector intDoc = (Vector)imports.get(o);
	    // we want to get the ImportImpl object if it exists
	    for(int i=0; i<intDoc.size(); i++) {
		Object obj = intDoc.elementAt(i);
    		if(obj instanceof ImportImpl) {
    		    interfaceDef = ((ImportImpl)obj).getDefinition();
    		}
	    }
	}

As you can see, I obtain a Definition object for the WSDL implementation from the implementation URL I gleaned while perusing the UDDI registry. I then obtained another Definition object, this one representing the WSDL interface, by searching all the imports defined in the implementation WSDL. A well-formed implementation WSDL should have an import pointing to its corresponding WSDL interface.

It's simple now to find the target namespace which to be passed to Axis. I just make a call to Definition.getTargetNamespace() on the WSDL implementation object and that's it. Next, let's find the port which contains the operations I want to invoke. This is done in Listing 6.


Listing 6. DynamicInvoke.java - line 195
		
    // great we've got the WSDL definitions now we need to find the PortType so
	// we can find the methods we can invoke
	Vector allPorts = new Vector();
        Map ports = interfaceDef.getPortTypes();
	s = ports.keySet();
	it = s.iterator();
	while(it.hasNext()) {
        Object o = it.next();
	    Object obj = ports.get(o);
	    if(obj instanceof PortType) {
    		allPorts.add((PortType)obj);
	    }
	}	
	// now we've got a vector of all the port types - normally some logic would
	// go here to choose which port type we want to use but we'll just choose 
	// the first one
	PortType port = (PortType)allPorts.elementAt(0);
	List operations = port.getOperations();

I get all the ports declared in the interface WSDL and just choose the first one. Of course if this were a real-world application I would want to develop some type of algorithm to choose the desired port and operation. However, in the interest of brevity, I won't do this.

Now that I've chosen the port to use, I find the service name and port name which I will supply to Axis. I do this by finding the binding in the WSDL interface which corresponds to the chosen port. Then I glean the service and port in the WSDL implementation that provides an endpoint for this binding.

You may find this a bit overcomplicated, so I will simplify it. Take a look at the WebMath_Impl.wsdl file and the WebMath_Interface.wsdl file which can be found in the WebMath\webapp directory. You can see in the implementation file that there is a <service> tag which contains a <port> tag. In the interface file you can see that there is a <binding> tag which contains an <operation> and also a <port> tag which represents the port I have already chosen. In both of these files there could feasibly be several of each of these so what I need to do is ensure that the service I choose in the implementation file contains the port which refers to the binding which contains the chosen port in the interface file.

In other words, I need to make sure that the binding I choose in the interface WSDL contains the same port I have already chosen (in the code snippet above). Then I need to make sure that the service chosen in the implementation WSDL contains a port which has a binding attribute value which is the same as the interface WSDL binding's name attribute value. If these are the same, then I've found the service and binding which refer to my chosen port.

See what the above algorithm looks like in Listing 7.


Listing 7. DynamicInvoke.java - line 215
		
	// let's get the service in the WSDL impl which contains this port
	// to do this we must first find the QName of the binding with the 
	// port type that corresponds to the port type of our chosen part
	QName bindingQName = null;
	Map bindings = interfaceDef.getBindings();
	s = bindings.keySet();
	it = s.iterator();
	while(it.hasNext()) {
	    Binding binding = (Binding)bindings.get(it.next());
	    if(binding.getPortType()==port) {
    		// we've got our binding
    		bindingQName = binding.getQName();
	    }
	}
	if(bindingQName==null) {
	    throw new WSDLException(WSDLException.OTHER_ERROR,
                        "No binding found for chosen port type.");         
	}
	// now we can find the service in the WSDL impl which provides an 
	// endpoint for the service we just found above
	Map implServices = implDef.getServices();
	s = implServices.keySet();
	it = s.iterator();
	while(it.hasNext()) {
	    Service serv = (Service)implServices.get(it.next());
	    Map m = serv.getPorts();
	    Set set = m.keySet(); 
	    Iterator iter = set.iterator();
	    while(iter.hasNext()) {
    		Port p = (Port)m.get(iter.next());
    		if(p.getBinding().getQName().toString().equals(
                                        bindingQName.toString())) {
    		    // we've got our service store the port name and service name
    		    portName = serv.getQName().toString();
    		    serviceName = p.getName();
    		    break;
        	}
	    } 
	    if(portName != null) break;
	}

By executing the above code I have gleaned the port name and service name that I need to pass to Axis. Now the only additional pieces of information I need are the method name and the parameters to pass into the method call.

Discovering the parameters and the Java type (that is, Java class, or native type) they map to is done in Listing 8.


Listing 8. DynamicInvoke.java - line 256
		
    // ok now we got all the operations previously - normally we would have some 
    logic here to
	// choose which operation, however, for the sake of simplicity we'll just 
	// choose the first one
	Operation op = (Operation)operations.get(0);
	operationName = op.getName();
	// now let's get the Message object describing the XML for the input and output
	// we don't care about the specific type of the output as we'll just cast it to 
	an Object
	Message inputs = op.getInput().getMessage();
	// let's find the input params 
	Map inputParts = inputs.getParts();
	// create the object array which Axis will use to pass in the parameters
	inputParams = new Object[inputParts.size()];
	s = inputParts.keySet();
	it = s.iterator();
	int i=0;
	while(it.hasNext()) {
	    Part part = (Part)inputParts.get(it.next());
	    QName qname = part.getTypeName();
	    // if it's not in the http://www.w3.org/2001/XMLSchema namespace then
	    // we don't know about it - throw an exception
	    String namespace = qname.getNamespaceURI();
	    if(!namespace.equals("http://www.w3.org/2001/XMLSchema")) {
   		throw new WSDLException(
                    WSDLException.OTHER_ERROR,"Namespace unrecognized");
	    }
	    // now we can get the Java type which the the QName maps to 
	    // we do this by using the Axis tools which map WSDL types
            // to Java types in the wsdl2java tool
	    String localPart = qname.getLocalPart();
	    javax.xml.rpc.namespace.QName wsdlQName = 
                    new javax.xml.rpc.namespace.QName(namespace,localPart);
	    TypeMapping tm = DefaultTypeMappingImpl.create();
	    Class cl = tm.getClassForQName(wsdlQName);
	    // if the Java type is a primitive, we need to wrap it in an object
	    if(cl.isPrimitive()) {
    		cl = wrapPrimitive(cl); 
	    }
	    // we could prompt the user to input the param here but we'll just
	    // assume a random number between 1 and 10. First we need to 
	    // find the constructor which takes a string representation of a number
	    // if a complex type was required we would use reflection to break it 
	    // down and prompt the user to input values for each member variable
            // in Object representing the complex type
	    try {
    		Constructor cstr = cl.getConstructor(
                        new Class[] { Class.forName("java.lang.String") });
    		inputParams[i] = cstr.newInstance(
                        new Object [] { ""+new Random().nextInt(10) });
	    } catch(Exception e) {
    		// shoudn't happen
   		e.printStackTrace();
	    }
	    i++;
	}	

Let me break down what's happening here. I already have an Operation object which I got from the call to Port.getOperations() I made on the port object. From this a simple call to Operation.getName() will yield the method name.

I now need to get the Message object for the <input> tag which is referred to by the chosen port. The <input> tag specifies the parameters that should be passed in to the method call. This is done by the code Operation.getInput().getMessage(). Once I've gleaned this, I then need to find all the parts the Message object contains.

I iterate through the parts and make sure that the namespace is the XML Schema namespace, so I'm sure I can map the part type to Java code. In a real-world application, of course, this would be expanded to support other types possibly as well.

I then switch over to a little bit of Axis in order to find to which Java class the type maps. Axis contains a TypeMapping.getClassForQName() method which is used in its wsdl2java tool. All I need to do is create a javax.xml.rpc.namespace.QName object which represents the type. Once I've made the call, then I have a Java Class object representing the type.

If the Class is a primitive type then I need to wrap it in a Java object. The code to do this is not presented here since it is rather simple, however, if you want to look at it it can be found in DynamicInvoke.java.When I have this Java object I will pass to Axis, I need to instantiate it, supply a value to the constructor and then store it. I find the constructor for the object using reflection and instantiate it providing a random number between 1 and 10. Of course, if this were more than a demo some other logic would choose the parameter, perhaps prompting the user to supply the information. Finally, I store the instantiated object in an Object array.

Phew! I've gathered all the information needed to invoke the service. Now I can use an Axis call to invoke the Web service.

Invoking the Web service with Axis

Invoking the Web service using Axis is a relatively straightforward procedure which is coded in Listing 9.


Listing 9. DynamicInvoke.java - line 365
		
    public void axisInvoke(String targetNamespace, String serviceName, 
            String portName, String operationName, Object[] inputParams, 
            String implURI) {
    	try {
    	    // first, due to a funny Axis idiosyncrasy we must strip portName of
    	    // it's target namespace so we can pass it in as 
            // targetNamespace, localPart
    	    int index = portName.indexOf(":",
                    portName.indexOf("http://")+new String("http://").length());
    	    String portNamespace = portName.substring(0,index);
   	        portName = portName.substring(
                        index==0?index:index+1); // to strip the :
    	    javax.xml.rpc.namespace.QName serviceQN = 
    		new javax.xml.rpc.namespace.QName( portNamespace, portName );
    	    org.apache.axis.client.Service service = 
    		new org.apache.axis.client.Service(new URL(implURI), serviceQN);
    	    javax.xml.rpc.namespace.QName portQN = 
    		new javax.xml.rpc.namespace.QName( targetNamespace, serviceName );

    	    // This Call object will be used the invocation
    	    Call call = (Call) service.createCall();

    	    // Now make the call...
    	    System.out.println("Invoking service>> " + serviceName + " <<...");
    	    call.setOperation( portQN, operationName );
    	    Object ret = (Integer) call.invoke( inputParams );
   	        System.out.println("Result returned from call to "+
                                            serviceName+" -- "+ret);
    	} catch(java.net.MalformedURLException e) {
    	    System.out.println("Error invoking service : "+e);
    	} catch(javax.xml.rpc.ServiceException e2) {
    	    System.out.println("Error invoking service : "+e2);
    	} catch(java.rmi.RemoteException e3) {
    	    System.out.println("Error invoking service : "+e3);
    	}
   }

So what am I doing here? Well, first I create a QName representing the port name. Axis doesn't provide a constructor that creates a QName from a complete namespace so I have to split the port name into the namespace and local part. I then create a Service object supplying the URL of the WSDL implementation and the QName representing the port name.

I then create a another QName object, this time representing the service name. I now create a Call object from the Service object. I set the operation this Call object will invoke by passing in the port QName and the operation name for it to invoke. And finally, I actually invoke the operation passing in the Object[] parameters created while parsing the WSDL. That's it! I now have the return value as a Java Object and print it out.


Summary

I hope you've understood all the code I've gone through in this article. It's a bit heavy, but having said that, understanding how uddi4j, wsdl4j, and Axis APIs work is important to effectively programming customized Web services clients. The best thing now is to play with the code and try to explore the information you can find from a UDDI registry or WSDL document. If you're still a bit fuzzy about the structure of the UDDI elements or the WSDL then try reading up on them. There are some excellent resources on these topics. Once again, if you have any questions regarding any of the content presented in this article, please post a comment in the Web Services Technical forum.


Resources

About the author

Damian Hagge works at IBM's Hursley Development Laboratories. Although he currently lives in the UK, he originated five hours counterclockwise from his present location just above the forty-ninth parallel (that's Canada for those whose geography isn't up to scratch). Aside from working in Web services and writing long articles on obscure subject matter, Damian is a veritable encyclopedia of useless facts; for instance, did you know that a hippopotamus has the greatest per-square-inch jaw pressure of any animal and is the most dangerous animal outside of water? Damian also does some normal stuff like power lifting (if you call that normal!). You can contact Damian Hagge at hagge@uk.ibm.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=SOA and Web services
ArticleID=11722
ArticleTitle=Dynamic Discovery and Invocation of Web services
publish-date=08012001
author1-email=hagge@uk.ibm.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers