Visar inlägg med etikett jax-ws. Visa alla inlägg
Visar inlägg med etikett jax-ws. Visa alla inlägg

tisdag 22 mars 2011

Templating webservices with Velocity using Camel

When creating webservices a lot of people has objections against using JAXB for binding XML to objects. This may be because of performance reasons, allergy to generated code or simply a philosophical belief that you should not mix document centric services with an object oriented model. Whatever the reasons for not using JAXB you have I'll try to describe a possible other solution using Camel.

I created this laboration after viewing Adrian Trenamans excellent talk about CXF usage in camel, so I can and will not take credit for anything you'll see. In that talk he showed a little trick were you could use Velocity as the templating engine for generating your soap responses, without ever doing any marshalling or parsing of the XML documents.

His complete session could be found here, but do note that you need a registration at fusesource.com to see it. It is free, so if you're interested I urge you to check it out.


Defining our contract


As mostly all SOA architects would tell you should always try to start by defining a wsdl, creating the service top-down. Our intended service in this sample is going to be a very simple credit check service that accepts a customer and provides a credit score and a detailed description about the reasoning behind the score.

Our service only has one operation for now, called PerformCreditCheck:



The response is a simple message containing the Score as well as a Details field:



The Camel context


I've chosen to implement the route using the Java DSL, so after setting up the CXF endpoint in the camel context I point the package scan to the java package where my route is. The complete camel-context.xml is as follows:
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cxf="http://camel.apache.org/schema/cxf"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd">

<import resource="classpath:META-INF/cxf/cxf.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
<import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml"/>

<cxf:cxfEndpoint xmlns:c="http://billy.laborations/CreditCheckService/" id="creditCheckEndpoint"
address="http://localhost:9000/creditcheck"
wsdlURL="META-INF/wsdl/CreditCheckService.wsdl"
serviceName="c:CreditCheckService"
endpointName="c:CreditCheckServiceSOAP"
serviceClass="billy.laborations.creditcheck.DummyService">
<cxf:properties>
<entry key="schema-validation-enabled" value="true" />
</cxf:properties>
</cxf:cxfEndpoint>

<camelContext xmlns="http://camel.apache.org/schema/spring">
<package>billy.laborations.creditcheck</package>
</camelContext>

</beans>



As you can see I'm providing the wsdl for the service, its endpoint address as well as an implementation class.

The JAX-WS Endpoint


Looking at this class is where things are starting to get interesting. Since I don't want to deal with JAXB I chose the JAX-WS Provider type of service which accepts arbitrary messages. Any type of message that hits this service will kick of the invoke() method, leaving XML parsing etc up to the developer. If you look at the method however, you'll notice that it is set up to always throw an exception, how could it possibly work? Well, it seems that Camel hijacks this method and kicks of the route instead, but this class still needs to be existing in order for the CXF runtime to initialize.


package billy.laborations.creditcheck;

import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Provider;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceProvider;

@WebServiceProvider(portName="CreditCheckPort", serviceName="CreditCheckService", targetNamespace="http://billy.laborations/CreditCheckService/")
@ServiceMode(Mode.PAYLOAD)
public class DummyService implements Provider<StreamSource> {

public StreamSource invoke(StreamSource request) {
throw new UnsupportedOperationException("Not implemented yet.");
}
}



Routing


The route is implemented as:


package billy.laborations.creditcheck;

import javax.xml.transform.stream.StreamSource;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.builder.xml.Namespaces;
import org.apache.cxf.message.MessageContentsList;


public class CreditCheckRoute extends RouteBuilder {

public void configure() {
// lets define the namespaces we'll need in our filters
Namespaces ns = new Namespaces("c", "http://billy.laborations/CreditCheckService/");

from("cxf:bean:creditCheckEndpoint").routeId("CreditCheckRoute")
.process(new Processor(){
public void process(Exchange exchange) throws Exception {

//Cxf stores requests in a MessageContentList. The actual message is the first entry
MessageContentsList messageList = (MessageContentsList)exchange.getIn().getBody();
exchange.getIn().setBody(messageList.get(0), String.class);
}
})
.wireTap("seda:audit")
.setHeader("FirstName", xpath("/c:Customer/FirstName").resultType(String.class).namespaces(ns))
.setHeader("LastName", xpath("/c:Customer/LastName").resultType(String.class).namespaces(ns))
.setHeader("SocialNr", xpath("/c:Customer/SocialNr").resultType(Integer.class).namespaces(ns))
.process(new CreditCheckCalculator())
.to("velocity:META-INF/velocity/creditCheckResponse.vm")
.convertBodyTo(StreamSource.class)
;


from("seda:audit")
.inOnly()
.to("log:audit")
.to("file:target/audit/?fileName=${date:now:yyyyMMdd}.txt&fileExist=Append");
}
}



As you can see the route uses XPath to set some headers based on the incoming values. This is done so that our service (CreditCheckCalculator) has easy access to them.

The business service


I've kept the service simple in this case, but the interesting part is that it's using the camel headers for getting and setting the data. You don't have to implement the service as a camel Processor, there are a lot of ways to call i.e. an existing POJO using bean referencing, but I'll leave that for now.


package billy.laborations.creditcheck;

import org.apache.camel.Exchange;

public class CreditCheckCalculator implements org.apache.camel.Processor{

public void process(Exchange exchange) throws Exception {
String firstName = (String)exchange.getIn().getHeader("FirstName");
String lastName = (String)exchange.getIn().getHeader("LastName");
int socialNr = (Integer)exchange.getIn().getHeader("SocialNr");

int creditScore;
String details;

if(firstName.equalsIgnoreCase("Billy")){
creditScore = 99;
details = firstName + " is a really good customer!";
}else{
creditScore = 1;
details = firstName + " should not be trusted!";
}

exchange.getIn().setHeader("CreditScore", creditScore);
exchange.getIn().setHeader("Details", details);
}
}



Generating the response


Why are the headers a good place to store the result then? Well, in our case it's because we have easy access to them from the velocity template:


<CreditScore xmlns="http://billy.laborations/CreditCheckService/">
<Score>$headers.CreditScore</Score>
<Details>$headers.Details</Details>
</CreditScore>


The trick here is that we're templating our response without touching any XML API's. The velocity template will simply return a String object which we can then transform to a StreamSource before handing it back to the JAX-WS endpoint.

Final thoughts


Well, that sums up my experiment. It might look like a complex setup for a very simple service, but the benefits of using templating are:

* Performance - No need to parse the request or response messages
* WS stack - Exposing the route as an CXF endpoints gives us all the power of soap handlers and CXF injectors for i.e. adding security etc.
* Adding functionality to the route - The route can easily be extended to support other EIP patterns, such as custom auditing implemented by the wiretap in our example.

If you are interested in the code, I'll make sure to upload it. Otherwise thank you for this time!

söndag 24 oktober 2010

First bump in the SCA investigation

During evalutations and investigations you sometimes come to a point where you hit the wall and finds an issue that leaves a big question mark in your head. Suffice to say I just hit one of those moments...

If you've followed my previous posts you know that I've recently started to look into how the SCA Tuscany runtime and composition model could be utilized for building transport/implementation neutral services at my client.

The existing strategy is based on building webservices with JAX-WS, Java's standard WebService framework. One great feature of any WS framework is the ability to define message interceptors/handlers that will be injected before the message receives the actual service. These handlers have access to the entire soap message, including the soap headers. So you could i.e. define a company-wide custom soapheader with meta-data which should be acted upon by each and every service before (or even after) invocation.

This takes care of non-functional requirements such as traceability, security, end-to-end header propagation etc, since those aspects of the call can be separated from the actual business service.

Looking at the SCA spec and what the tooling provides I have yet to find the similar function in the SCA world. Even if you i.e. expose a component as a webservice binding I cant find any way to specify my chain of handlers that should get invoked for incoming requests.

Looking into the SCA 1.1 specification it states that jax-ws handlers are not included in the standard, but should/could be implemented by vendor implementations. From what I can see IBM has not chosen to do so in the current version of the SCA feature pack runtime nor tooling.
This seems a bit strange to me since I know that the BPM (0.9 SCA) products has had the support for adding handlers to a webservice export for a long time.

I'll definately be going to dig into the subject more, since it could prove to be a big no-go for us as we otherwise would have to revise our entire call-chain strategy for i.e. composite services.

To you readers out there, have you had experiences with interceptors/handlers in the SCA context? I might be missing some obvious features that you know about? If so, I'd be very delightful for all advices you could provide.

Over and out...