ctrl-alt-Development
Your hotkey to alternative software development
Generating a new client using the WSDL
Create a new Maven quick start project and call it CXFTestWsdlClient or something. Copy and paste the compiler configuration and the dependencies from the previous project and place them in the pom.xml. Generate the eclipse integration files if you want those.
Assuming Tomcat is still running you can get the WSDL using the well known url http://localhost:8080/CXFTestService-1.0-SNAPSHOT/birthday?wsdl The question now is: how do we generate some client code for. Fortunately there is a Maven plugin that does just that. The cxf-codegen-plugin .
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>2.4.0</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${basedir}/src/main/generated</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>http://localhost:8080/CXFTestService-1.0-SNAPSHOT/birthday?wsdl</wsdl>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
The configuration above will generate the required java classes using the given WSDL in the 'generated' folder when you give the 'mvn compile' command. If it doesn't work because of a JAXB error, the most likely error is inside the WSDL. The easiest way of solving this is to download the WSDL and use that instead to find the error, adjusting it as you go. You'll ultimately conclude that one of the annotations in the server is incorrect :)
If you want your eclipse workspace show the newly generated classes you can update your build path with 'mvn eclipse:eclipse'
So, what's inside that directory. Well, aside from Java representations of all elements in the WSDL also an ObjectFactory to easily create correctly initialized JAXB elements (name spaces and such) and the BirthdayCalendarService, an almost complete web-service client.
Time to build a unit test to see if it works:
package nl.cad.cxf.testservice;
import java.text.SimpleDateFormat;
import java.util.List;
import junit.framework.TestCase;
public class TestClient extends TestCase {
public void testClient() throws Exception {
Birthday bc=new BirthdayCalendarService().getBirthdayPort();
SimpleDateFormat sdf=new SimpleDateFormat("dd-MM-yyyy");
List<Birthday_Type> br=bc.getBirthdaysInMonth(2);
if (br==null||br.size()==0) {
bc.addBirthday("Erik",sdf.parse("21-02-1971"));
}
List<Birthday_Type> b=bc.getBirthdaysInMonth(2);
assertEquals(1,b.size());
assertEquals("Erik",b.get(0).getName());
assertEquals(21,b.get(0).getDayOfMonth());
//
System.out.println(b.get(0).getName()+" "+b.get(0).getDayOfMonth());
}
}
The code is almost the same, except for the birthday date, which suddenly changed type to XMLGregorianCalendar.. Which is not a very convenient class but is actually correct as its the default mapping for date and time in XML. Its possible to write some converter code to change a java.util.Date to a XMLGregorianCalendar but that will make the unit test a lot longer. Maybe its a better idea to explain to JAXB that we want to use a Date.
Using a so called custom binding file (another piece of XML) we can tell JAXB which Java types to use for a particular piece of XML. Its possible to add these instructions to the XSD or WSDL (as an XML annotation on the element) or use a separate binding file. We'll use the latter in a file called jaxws-custom- bindings.xml :
<jaxws:bindings wsdlLocation="http://localhost:8080/CXFTestService-1.0-SNAPSHOT/birthday?wsdl"
xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<jaxws:bindings node="wsdl:definitions/wsdl:types/xs:schema[@targetNamespace='http://testservice.cxf.cad.nl/']">
<jxb:globalBindings xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<jxb:javaType name="java.util.Date" xmlType="xs:dateTime"
parseMethod="org.apache.cxf.tools.common.DataTypeAdapter.parseDateTime"
printMethod="org.apache.cxf.tools.common.DataTypeAdapter.printDateTime"/>
</jxb:globalBindings>
</jaxws:bindings>
</jaxws:bindings>
When placed in the ./src/main/resources folder, we can reference it in the plugin configuration in the pom.xml:
<wsdlOption>
<wsdl>http://localhost:8080/CXFTestService-1.0-SNAPSHOT/birthday?wsdl</wsdl>
<extraargs>
<extraarg>-b</extraarg>
<extraarg>${basedir}/src/main/resources/jaxws-custom-bindings.xml</extraarg>
</extraargs>
</wsdlOption>
Now the code needs to be regenerated. The easiest way of doing this is just to empty the 'generated' source folder, including the hidden file there. When the code is regenerated and tested (by the 'mvn test' command) you'll notice that the unit test now does compile.
A disadvantage of the above solution is that the web-service itself needs to be available when compiling. When the webservice isn't available it becomes impossible to do a build. Not very convenient, so its better to download the WSDL to the /src/main/resources folder and then use the wsdlLocation to reference it:
<jaxws:bindings wsdlLocation="BirthdayCalendarService.wsdl">
...
</jaxws:bindings>
and the pom:
<wsdlOption>
<wsdl>${basedir}/src/main/resources/BirthdayCalendarService.wsdl</wsdl>
<extraargs>
<extraarg>-b</extraarg>
<extraarg>${basedir}/src/main/resources/jaxws-custom-bindings.xml</extraarg>
</extraargs>
</wsdlOption>
Implementing a service using the generated code from the WSDL
The only thing that we didn't do yet is to create a web Service implementation using the generated code. Fortunately this is not really difficult as we've already got the generated code and the implementation as well :) Its just a matter of rewriting and configuring. As basis for the implementation we now use the generated interface Birthday. There are a few minor changes to the original implementation:
- instead of using an array a list is returned.
- the original BirthdayType is now called Birthday_Type (but why?) and doesn't have the useful constructor.
Because its unwise to change generated code (what if its regenerated) we need to make the modifications somewhere else. Another thing is that creation of generated objects always uses the ObjectFactory type (because of name space issues). The rewritten service will look like this:
package nl.cad.cxf.testservice;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
public class BirthdayCalendarImpl implements Birthday {
private List<Birthday_Type> birthdays=new ArrayList<Birthday_Type>();
public synchronized void addBirthday(String name, Date date) {
Birthday_Type b=new ObjectFactory().createBirthday_Type();
Calendar c = Calendar.getInstance();
c.setTime(date);
b.setDayOfMonth(c.get(Calendar.DAY_OF_MONTH));
b.setMonth(c.get(Calendar.MONTH) + 1);
b.setName(name);
birthdays.add(b);
}
public synchronized List<Birthday_Type> getBirthdaysInMonth(int month) {
List<Birthday_Type> results=new ArrayList<Birthday_Type>();
Birthday_Type[] bs=birthdays.toArray(new Birthday_Type[birthdays.size()]);
for (Birthday_Type b:bs)
if (b.getMonth()==month) results.add(b);
return results;
}
}
What remains is the changing of the unit test so that we can prove that our implementation is really working. Because the changing of the URL of the client is a bit awkward (its in the WSDL) we need to make sure the service runs where the client expects it. Make sure Tomcat is stopped :) The additional code to create the service is as follows:
static {
JaxWsServerFactoryBean svrFactory = new JaxWsServerFactoryBean();
svrFactory.setServiceClass(Birthday.class);
svrFactory.setAddress("http://localhost:8080/CXFTestService-1.0-SNAPSHOT/birthday");
svrFactory.setServiceBean(new BirthdayCalendarImpl());
svrFactory.setServiceName(BirthdayCalendarService.SERVICE);
svrFactory.create();
}
After a final 'mvn test' you'll know that this also works.
Well done, you've reached the end of the tutorial. What remains is the summary and conclusion.