Mule and WSO2 Registry integration
In this two part article we’ll look at how we can use the WSO2 registry (http://wso2.com/products/governance-registry/) as the service repository for services provided by the Mule ESB (http://www.mulesoft.org/). In this first part we’ll show how Mule can register its services with the WSO2 registry using WS-Discovery, in the second article we’ll look at how other clients can search the WSO2 registry and consume the services. For this article, as for the second one, all the sources can be downloaded. Just look at the bottom of this article for the links.
So let’s get started. For this to work and for you to test with, we need to do a couple of things:
- Install WSO2 Registry and Mule
- Create a simple service that we expose through Mule
- Create a simple client that can register services with WSO2
- Hook into the lifecycle of Mule so that the services are registered on startup
First things first, lets install these two applications.
Install WS02 Registry and Mule
Installing the registry and the ESB is pretty easy. You can find the download for the WSO2 registry here (http://wso2.org/downloads/governance-registry) and for Mule here (http://www.mulesoft.org/download-mule-esb-community-edition). Both come packaged in various formats which can just extract into the directory of your choice. Now we’ll start these applications to see if they are installed correctly. For Mule you can use the following command:
Should there be any problems with the startup, the easiest thing to do is look at the information on the websites of these products. They both have extensive documentation and that should be able to solve and installation issues quickly.
Creating the simple service The simple service we’ll use is just a simple Hello Service as seen in most examples. We’ll create a java interface and an implementation class which we’ll expose using Mule’s CXF support. The interface for this service is shown below:
package manning.governance.wsdiscovery;
import javax.jws.WebService;
@WebService
public interface HelloWorld {
@WebResult(name="response")
String sayHi(@WebParam(name="hello") String text);
}
As you can see from this interface we’ve defined a very basic request/reply webservice, which just responds with an hello. The implementation is also very straightforward.
package manning.governance.wsdiscovery;
import javax.jws.WebService;
@WebService(endpointInterface = "manning.governance.wsdiscovery.HelloWorld")
public class HelloWorldImpl implements HelloWorld {
public String sayHello(String text) {
return "Hello " + text;
}
}
Now that we’ve created a jax-ws webservice, lets expose this service using Mule. For this we create a simple flow (a Mule 3 construct):
<flow name="simpleFileFlow">
<inbound-endpoint address="http://localhost:18080/hello" />
<cxf:jaxws-service serviceClass="manning.governance.wsdiscovery.HelloWorldImpl"/>
<component class="manning.governance.wsdiscovery.HelloWorldImpl" />
</flow>
If you start Mule with this configuration you’ll have this webservice running on http://localhost:18080/hello. You can now use SOAP-UI or any other tool you use for testing a webservice to invoke the operation.
Creating the WS-Discovery client That was the easy part, the next thing we need to do is find a way to register services with WSO2 SOA Registry. For this we can use a WS-* standard named WS-Discovery (http://docs.oasis-open.org/ws-dd/discovery/1.1/pr-01/wsdd-discovery-1.1-spec-pr-01.html). With WS-Discovery it’s possible to register services in a standard compliant way and it’s also possible to query this same repository. This latter part is something I’ll show in the second part of the article.
Before we dive into the creation of a WS-Discovery client, lets first look at how WS-Discovery works. In WS-Discovery you’ve got a simple set of operations you can use to register and search services. Basically the four services exposed are:
- Hello: Register a service to a WS-Discovery server
- Bye: Deregister a service from a WS-Discovery server
- Probe: You can use a probe message to search for services with specific properties or simply return all the registered services
- Resolve: If you've already got an endpoint reference (either from an earlier probe or static configuration), you can use this endpoint reference to retrieve the details of this endpoint (for instance its transport address)
Besides these four operations WS-Discovery you can call it also defines two different ways you can call this service. You’ve got managed mode and ad-hoc mode. In managed mode, which is the one supported by the WSO-Registry, calls are made directly to the registry using unicast calls.
…
In this figure the registry plays the part of the “Discovery Proxy”. Besides the managed mode, there is also an ad-hoc mode.
…
In the ad-hoc mode initial discovery and registration are all done using multi-cast messages (for instance udp). There is an opensource java library (url of java ws-discovery) which supports this. But since we’re looking at how to integrate Mule and the WSO2 registry this is a bit out of scope.
Now back to the WS-Discovery client we need to write. To write a client for the unicase WS-Discovery proxy we need to have a WSDL. For this we have two options, we can use the WSDL provided by the WSO2 registry (can be found at http://localhost:9763/services/DiscoveryProxy?wsdl) or we can take the ones provided by the specification itself. Since the ones from the WSO2 registry uses xs-any elements, the created client stub won’t be that useful, since we still have to manually add the correct content.
<xs:element name="Hello">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="Hello" nillable="true" type="xs:anyType" />
</xs:sequence>
</xs:complexType>
</xs:element>
Therefore I took the specifications from here (http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01), removed the multicast operations, added a service and used that WSDL as the base to generate the client code from. If you’re interested in these final WSDLs you can download the complete project including all the files used at the end of this article. Now that we’ve got a good WSDL to start from, lets use Maven and WSDL2Java to generate our client stub. To do this we just add the following to the Maven build file.
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>2.2.3</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${basedir}/src/main/resources/wsdl/ws-discovery.wsdl</wsdl>
<extraargs>
<extraarg>-client</extraarg>
<extraarg>-noAddressBinding</extraarg>
</extraargs>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
Running the build now will generate the client code (we’ve specified this with the -client option) which we’ll use to call the discovery proxy. If you look closely at the plugin configuration you’ll notice the use of the “-noAddressBinding” argument. The reason for this is the following schema type definition:
<xs:element name="EndpointReference" type="tns:EndpointReferenceType" />
<xs:complexType name="EndpointReferenceType" mixed="false">
<xs:sequence>
<xs:element name="Address" type="tns:AttributedURIType" />
<xs:element ref="tns:ReferenceParameters" minOccurs="0" />
<xs:element ref="tns:Metadata" minOccurs="0" />
<xs:any namespace="##other" processContents="lax" minOccurs="0"
maxOccurs="unbounded" />
</xs:sequence>
<xs:anyAttribute namespace="##other" processContents="lax" />
</xs:complexType>
This defines an endpoint reference and comes from the WS-Addressing schema. If you parse a WSDL which includes such an endpointReference, an internal java type will be generated: javax.xml.ws.EndpointReference. The problem is that we can’t access the address part of this class. This poses a problem if we want to dynamically create an endpoint using other techniques than jax-ws. If you specify the -NoAdrressBinding argument, the internal class won’t be used and a normal javabeans based class is created.
With the code generated, we can create a simple WS-Discovery client by directly using the generated client (we could also have used CXFs dynamic proxy). To do this we just instantiate this client and configure it with the url the discovery proxy is running on:
private DiscoveryProxy getPort(String dicoveryProxyUrl) {
DiscoverProxyService service = new DiscoverProxyService();
DiscoveryProxy port = service.getDiscoverProxyPort();
((BindingProvider) port).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY
,dicoveryProxyUrl);
return port;
}
The port returned here provides the Hello, Bye, Probe and Resolve methods we can use to register and search for services. In this article we’ll focus on the registration part. For this we need to use the Hello operation. If we look at the WS-Discovery specification the Hello operation takes the following parameters.
<d:Hello ... >
<a:EndpointReference> ... </a:EndpointReference>
[<d:Types>list of xs:QName</d:Types>]?
[<d:Scopes>list of xs:anyURI</d:Scopes>]?
[<d:XAddrs>list of xs:anyURI</d:XAddrs>]?
<d:MetadataVersion>xs:unsignedInt</d:MetadataVersion>
</d:Hello>
-
- EndpointReference: Endpoint Reference for the Target Service and contains an "address" element (amongst others). This element however doesn't have to resolve to a network resolvable address. It's even recommended that this is a globally unique identifier which should remain constant regardless of network interfaces and lifecycle of this service. If this can't be resolved to a network address, which should be assumed, the network address should be specified in the d:XAddrs part of the Hello message.
- Types: describes the type of service this is. For instance d:employeeInformationService or d:accountServiceBasic and d:accountServiceAdvanced. You're pretty free to specify here whatever you want.
- Scopes: With scopes you can specify in which scopes your service should be used. For instance you could specify here that your service should be used only at a specific location, in a specific timeframe, or only for testing purposes. Once again there is no strict format for this, so you can easily adapt this to your specific scenario.
- XAddrs: This is the element which contains the endpoint addresses on which your service can be called. In contrast with the endpoint reference we saw earlier, this should be a network resolvable address.
- MetadataVersion: the version of this service. If for instance the interface changes the next time this service registers itself
Back to the source code and let’s see how we specify this in our own ws-discovery client.
public void hello(String endpointReference, List<String> addresses, List<QName> types, String scope, long version) {
try {
// create the parameter
HelloType parameters = new HelloType();
// first the endpointReference
EndpointReferenceType reference = new EndpointReferenceType();
AttributedURIType attr = new AttributedURIType();
attr.setValue(endpointReference);
reference.setAddress(attr);
parameters.setEndpointReference(reference);
// then the types
List<QName> typesList = parameters.getTypes();
typesList.addAll(types);
// and the addresses
List<String> addressesList = parameters.getXAddrs();
addressesList.addAll(addresses);
// and the scope
ScopesType scopesType = new ScopesType();
scopesType.setMatchBy(scope);
parameters.setScopes(scopesType);
// and finally the version
parameters.setMetadataVersion(version);
// now get the port and fire the operation
getPort(PROXY_URL).helloOp(parameters);
} catch (Exception e) {
// handle exception, for now just print it out
e.printStackTrace();
}
}
private DiscoveryProxy getPort(String dicoveryProxyUrl) {
DiscoverProxyService service = new DiscoverProxyService();
DiscoveryProxy port = service.getDiscoverProxyPort();
((BindingProvider) port).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, dicoveryProxyUrl);
return port;
}
The interesting code here is the one in the hello operation. This operation takes as parameters the information we’ve just discussed. All it does is fill in the generated HelloType parameter and uses the generated client to fire of the request. Nothing to special here. One thing to notice though is the way we get the port for a specific url. Normally when we get a generated client, the client uses the address specified in the WSDL, often though we need to be able to manually set the destination of the webservice. We can do this by manually changing the BindingProvider.ENDPOINT_ADDRESS_PROPERTY as is shown in this code sample.
At this point we’ve got a working WS-Discovery client, which we can already use from our unit tests to register an endpoint in the WSO2 registry. What is missing here though is integration with Mule. The scenario which we’ll follow in this example is that once Mule is started we want to register all the available endpoint in the service registry. In the next section we’ll show you how you can use this client from Mule to register services.
Hook into the lifecycle of Mule so that the services are registered on startup
The last part we need to do is hook into Mule’s lifecycle and register the endpoints when Mule is started. For this example we’ll register the services when Mule has started its context. In other words when we receive the MuleContextNotification.CONTEXT_STARTED event, we’ll get a list of all the endpoints and register those in the WSO2 registry. Mule provides a number of options to hook into the lifecycle, we can register a notification listener using a simple Spring Bean, change the way Mule is bootstrapped, or in this case create an agent that listens to specific events. To create an agent all we need to add to the mule configuration is the following:
<custom-agent name="test-custom-agent" class="org.smartjava.agent.notifications.DiscoveryAgent">
<spring:property name="discoveryUrl" value="http://localhost:9763/services/DiscoveryProxy"/>
</custom-agent>
This will register an agent which is started when Mule itself is started. The code for this agent isn’t that special. We implement a couple of interfaces to get the mule context and the notification and for the rest we just use the client code we discussed earlier. The interesting parts of the code are shown below.
public class DiscoveryAgent implements Agent, MuleContextAware, MuleContextNotificationListener<MuleContextNotification> {
private String discoveryUrl;
private MuleContext context;
private List<String> registeredEndpoints = new ArrayList<String>();
private DiscoveryClient client = new DiscoveryClient();
@Override
public void setMuleContext(MuleContext muleContext) {
this.context = muleContext;
try{
muleContext.registerListener(this,"*");
} catch (NotificationException e) {
e.printStackTrace();
}
}
@Override
public void onNotification(MuleContextNotification notification) {
if (notification.getAction() == MuleContextNotification.CONTEXT_STARTED) {
Collection<ImmutableEndpoint> endpoints = context.getRegistry().getEndpoints();
for (ImmutableEndpoint immutableEndpoint : endpoints) {
if (immutableEndpoint.getProperties() != null && immutableEndpoint.getProperties().containsKey("wsdiscovery.enableDiscovery")) {
String enableValue = (String) immutableEndpoint.getProperties().get("wsdiscovery.enableDiscovery");
if (enableValue.equals("true")) {
if (!registeredEndpoints.contains(immutableEndpoint.getAddress())) {
// get the version
String value = (String) immutableEndpoint.getProperties().get("wsdiscovery.version");
long version = Long.valueOf(value);
// get the endpoint name
String endpointReference = immutableEndpoint.getName();
// get the endpoint address
List<String> addresses = Arrays.asList(immutableEndpoint.getAddress());
// get the scope
String scope = (String) immutableEndpoint.getProperties().get("wsdiscovery.scope");
// get the types as qname
List<QName> types = stringToQName((String) immutableEndpoint.getProperties().get("wsdiscovery.types"));
// and make the call to the client
client.hello(endpointReference, addresses, types, scope, version);
// make sure we only register each endpoint one time
registeredEndpoints.add(immutableEndpoint.getAddress());
}
}
}
}
}
}
...
}
In the class definition you can see that we implement the Agent, MuleContextAware and the MuleContextNotificationListener interfaces. The Agent interfaces is required since we’re implementing a Mule Agent. The MuleContextAware interface is used to get access to the MuleContext which we use later on to get the endpoints. The last interface, the MuleContextNotificationListener, allows us to receive certain context related events. When a context event occurs the method onNotification is called. In this method, as you can see from the code, we do the following things:
- We check whether the event indicates the context is started
- If it's the correct event, we use the Mule context to get the list of registered endpoints
- We then check if the property "wsdiscovery.enableDiscovery" is set to true.
- If it is, we collect a number of other properties of the endpoint and use the client to call the hello method
- Finally we keep track of the registered addresses to avoid registring the same endpoint twice.
That’s it for the agent. The final part we need to do is configure Mule to start up our agent and configure the endpoints we want to register with the SOA Repository. The next listing shows the Mule configuration required for this.
<mule xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:spring="http://www.springframework.org/schema/beans"
xmlns:file="http://www.mulesoft.org/schema/mule/file"
xmlns:http="http://www.mulesoft.org/schema/mule/http"
xmlns:cxf="http://www.mulesoft.org/schema/mule/cxf"
xsi:schemaLocation="
http://www.mulesoft.org/schema/mule/cxf http://www.mulesoft.org/schema/mule/cxf/3.0/mule-cxf.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/3.0/mule.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/3.0/mule-http.xsd
http://www.mulesoft.org/schema/mule/file http://www.mulesoft.org/schema/mule/file/3.0/mule-file.xsd">
<custom-agent name="test-custom-agent" class="org.smartjava.agent.notifications.DiscoveryAgent">
<spring:property name="discoveryUrl" value="http://localhost:9763/services/DiscoveryProxy"/>
</custom-agent>
<notifications dynamic="true"/>
<flow name="simpleFileFlow">
<inbound-endpoint address="http://localhost:18080/hello" >
<properties>
<spring:entry key="wsdiscovery.enableDiscovery" value="true"/>
<spring:entry key="wsdiscovery.version" value="1"/>
<spring:entry key="wsdiscovery.scope" value="e:QA"/>
<spring:entry key="wsdiscovery.types" value="{http://example.org}Hello,{http://discovery.org}Example"/>
</properties>
</inbound-endpoint>
<cxf:jaxws-service serviceClass="manning.governance.wsdiscovery.HelloWorldImpl"/>
<component class="manning.governance.wsdiscovery.HelloWorldImpl" />
</flow>
</mule>
As you can see we add a simple properties element to the inbound endpoint on which we configure the ws-discovery specifics we’ve discussed earlier. That’s all you need to do. Now we can run this example (after you’ve started the WSO2 SOA Registry), and Mule will automatically register its services into the WSO2 Registry. Do check the registered service go to the SOA Registry admin page (https://localhost:9443/carbon/), log in and click on the metadata tab you can view the services that are available. Here you’ll see a service listed with a DiscoveredService_1286732385985 like name. This is the service that was addes from Mule. In the current view you won’t see much about the service that was registered. If you click on the ‘standard view’ link and then on the ‘Display as text’ link you can see the information we’ve registered.
…
Next week I’ll post the follow to this article where we will see how to discover services using WS-Discovery from Mule to the WSO2 Service registry.