ctrl-alt-Development
Your hotkey to alternative software development
Refactoring to a JAX-WS / JAXB Web service
Before you start, its probably a good idea to make a backup of the project so far :) Up until now we only used pure Java types, but now we're going to enhance our classes with annotations. Annotations add additional information to the class so that frameworks can do special things with them. In our case this is changing it into a web-service and being able to do Java<->XML data binding.
To change our service interface into a JAX-WS web-service is easy, we just add a few annotations:
package nl.cad.cxf.testservice;
import java.util.Date;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
/** A simple birthday calendar service. */
@WebService(name="birthday", targetNamespace="http://testservice.cxf.cad.nl/" )
public interface BirthdayCalendar {
/**
* Adds a birthday.
* @param name the name of the person.
* @param date the birthday.
*/
@WebMethod
void addBirthday(@WebParam(name="name") String name,@WebParam(name="date") Date date);
/**
* returns all the birthdays in one month.
* @param month the month (1..12).
* @return all the birthdays in that month.
*/
@WebMethod
Birthday[] getBirthdaysInMonth(@WebParam(name="month")int month);
}
The WebService annotation declares the interface as a web-service contract, the WebMethod annotations tell that the given method must be a part of that web service and the WebParam annotations make sure the elements in the XML get readable names.
There is a slight problem with our Birthday interface however. JAXB doesn't like interfaces, so it must be changed to a top level class (using BirthdayImpl). The reason JAXB can't deal with interfaces makes kind of sense because you can't make instances of interfaces. AEGIS data binding uses proxies which is exactly what we're trying to avoid here.
Refactor the static inner class BirthdayImpl to a top level class Birthday and add the missing setters. In order to make JAXB recognize Birthday as an XmlType the class needs to be annotated with the @XmlType annotation:
package nl.cad.cxf.testservice;
import java.util.Calendar;
import java.util.Date;
import javax.xml.bind.annotation.XmlType;
@XmlType
public class Birthday {
private String name;
private int dayOfMonth;
private int month;
public Birthday() {
}
public Birthday(String name, Date birthDay) {
this.name = name;
Calendar c = Calendar.getInstance();
c.setTime(birthDay);
dayOfMonth = c.get(Calendar.DAY_OF_MONTH);
month = c.get(Calendar.MONTH) + 1; // 1..12
}
public int getMonth() {
return month;
}
public int getDayOfMonth() {
return dayOfMonth;
}
public String getName() {
return name;
}
public void setDayOfMonth(int dayOfMonth) {
this.dayOfMonth = dayOfMonth;
}
public void setMonth(int month) {
this.month = month;
}
public void setName(String name) {
this.name = name;
}
}
The BirthdayCalendarImpl service implementation requires no changes fortunately. Which is good, because we can now start changing our unit tests to the new situation. There are few changes. Aegis data binding can be removed and we'll use the JaxWsProxyFactoryBean instead. Also we'll use the JaxWsServerFactoryBean instead of the other one. We can now also remove the dependency on 'cxf-rt-databinding-aegis' from the pom. Less Jar's is more :)
After this last change, its time to test the JAX-wS web-service. Before you give the 'mvn test' instruction, it may be a good idea to comment out the IntegrationTest.
Running the refactored web-service in a web container.
The unit test works. Good, only we now need to alter the beans.xml configuration so that the web-service will be loaded correctly. This works along the same lines as the unit test. Instead of using the 'simple:server' tag, we'll now use the 'jaxws:endpoint' tag. Don't forget to change the name spaces, look at the code further on.
When this is done, its just a matter of doing another 'mvn clean install' and deploy the resulting war file on Tomcat. Don't forget to remove the previously unpacked one otherwise thing may happen you don't expect :)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:soap="http://cxf.apache.org/bindings/soap"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.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-servlet.xml"/>
<jaxws:endpoint id="birthdayCalendarService"
implementor="nl.cad.cxf.testservice.BirthdayCalendarImpl"
implementorClass="nl.cad.cxf.testservice.BirthdayCalendar"
address="/birthday"/>
</beans>
After starting Tomcat you can request the WSDL again via http://localhost:8080/CXFTestService-1.0-SNAPSHOT/birthday?wsdl to check if the web-service appears to be working. However this is not a proper integration tests. Its about time we fixed that by changing our integration test. This goes like before, only with one sneaky detail.. An empty array will return in JAX-WS a null value instead of an empty array. This is because of how collections are mapped to and from XML. JAXB gives preference to Lists instead of Arrays. We'll see some more about that later.
package nl.cad.cxf.testservice;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import junit.framework.TestCase;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
public class IntegrationTest extends TestCase {
protected BirthdayCalendar newBirthdayClient() {
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(BirthdayCalendar.class);
factory.setAddress("http://localhost:8080/CXFTestService-1.0-SNAPSHOT/birthday");
return (BirthdayCalendar) factory.create();
}
public void testService() throws ParseException {
BirthdayCalendar bc=newBirthdayClient();
SimpleDateFormat sdf=new SimpleDateFormat("dd-MM-yyyy");
Birthday[] br=bc.getBirthdaysInMonth(2);
if (br==null||br.length==0) {
bc.addBirthday("Erik",sdf.parse("21-02-1971"));
}
Birthday[] b=bc.getBirthdaysInMonth(2);
assertEquals(1,b.length);
assertEquals("Erik",b[0].getName());
assertEquals(21,b[0].getDayOfMonth());
}
}
Where are we now?
We have made a JAX-WS based web-service with the contract defined in classes and annotations. Sometimes however, you don't have these classes and annotations available. How can a web-service client be made without them ? This brings us to the final part of this tutorial, contract first development using the WSDL.