I just spent quite some time to create a prototype to tests some stuff about web service security. But for that, I first needed to set up another environment.
this is going to be a long post, so brace yourself... Let's start with a quick overview of the project.
Overview
goal : setup a web application running with tomcat to provide a simple web service. It is going to be a simple greeter.
- Create the "contract" of the web service, ie the wsdl file.
- Generate a skeleton of java code to build the web service and implement it.
- Configure the web application
- Deploy and test
Create the contract
The webservice is going to be name greeter and the wsdl file greeter.wsdl. I used wsdl editor, an eclipse plugin to do that. It is quite intuitive to create a simple web service using the graphical interface. Watch out for the service definition (usually towards the end of the file) :
<wsdl:service name="Greeter"> <wsdl:port binding="tns:GreeterSOAP" name="GreeterSOAP"> <soap:address location="http://localhost:8080/<CONTEXT>/<SERVLET_ENDPOINT>/<WSNAME>"/> </wsdl:port> </wsdl:service>
where <CONTEXT> is going to be the name of your web application, <SERVLET_ENDPOINT> is going to be used to configure the mapping between URL and java classes, and <WSNAME> is the name name of your webservice.
Generate the java files
Running maven with the cxf plugin should produce an interface called Greeter.java. Any implementation of this class (I used GreeterImpl.java) will do. Some details about the pom file needed to compile :
<properties> <camel.version>2.8.1</camel.version> <spring.version>3.0.6.RELEASE</spring.version> <cxf.version>2.4.2</cxf.version> </properties><build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration></configuration> </plugin> <plugin> <artifactId>maven-eclipse-plugin</artifactId> <version>2.8</version> <configuration></configuration> </plugin> <!-- Generate code based on the WSDL --> <plugin> <groupId>org.apache.cxf</groupId> <artifactId>cxf-codegen-plugin</artifactId> <version>${cxf.version}</version> <executions> <execution> <id>generate-sources</id> <phase>generate-sources</phase> <configuration> <sourceRoot>${basedir}/src/main/java</sourceRoot> <wsdlRoot>${basedir}/src/main/resources/wsdl</wsdlRoot> <includes> <include>*.wsdl</include> </includes> </configuration> <goals> <goal>wsdl2java</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> </dependencies> </plugin> <!-- This plugin is configured in order to scan resources in the webapp folder in order to substitute profile properties --> </plugins> </build></pre>
Eclipse and maven plugin to compile. the cxf plugin generate the java code from the wsdl (here in java/main/resources/wsdl).
Configure the web application
Here it can be tricky... First, we're going to need another maven plugin to generate the .war :
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.0.2</version> <configuration> <webResources> <resource> <filtering>true</filtering> <directory>${project.basedir}/src/main/webapp/</directory> <includes> <include>**/*.xml</include> </includes> </resource> </webResources> </configuration> </plugin>You saw that the plugin is looking for every xml files in the directory /src/main/webapp/**. So we're going to put the web.xml file there (precisely in /src/main/webapp/META-INF/). The headers have nothing special :
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" metadata-complete="true" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">Then, the cxf plugin is using spring so we need to declare a spring context configuration file along with a listener for this context :
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-ws.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>And then, add the cxf servlet. At this point, you have to remember the variable you put in your wsdl file, the <SERVLET_ENDPOINT>.
<servlet> <servlet-name>CXFServlet</servlet-name> <display-name>CXF Servlet</display-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/<SERVLET_ENDPOINT>/*</url-pattern> </servlet-mapping>So everything with the url localhost:8080/<CONTEXT>/<SERVLET_ENDPOINT>/whatever is going to be handle by the cxf servlet. Now let's write thespring-ws.xml file to configure spring and the endpoints.
<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:jaxrs="http://cxf.apache.org/jaxrs" xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<context:annotation-config /> <import resource="classpath:META-INF/cxf/cxf.xml" /> <jaxws:endpoint id="greeterWS" implementor="<WEB_SERVICE_CLASS>" address="/greeter" />
</beans>
What does that do ? First, import a resource (which is actually inside a .jar imported by maven, see the final pom file at the end) and then, declare an endpoint for the webservice. the variable <WEB_SERVICE_CLASS> should be the fully qualified name of your class without the .java extension. This class is the one implementing the interface Greeter.java, in my case it's still GreeterImpl.java
Here are the dependencies for the cxf plugin :
<dependencies><!-- CXF --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-core</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf.version}</version> </dependency> </dependencies></pre>
Deploy and test
And here we are, finally. Run the goal install with maven. (don't forget to put the packaging to war)
<packaging>war</packaging>Then, copy the resulting war in your apache-tomcat/webapp folder and restart it. It should automatically create a folder with the same name as the .war. The URL localhost:8080/<CONTEXT>/<SERVLET_ENDPOINT>/greeter should display an xml message (error message probably if there is an expected input). You can try the webservice using SoapUI.
And it's done. The main pitfall is the dependencies of the cxf plugin in the pomfile.