![]() |
![]() |
Apache Maven & Failsafe |
![]() |
Apache Tomcat |
Disclosure
Before you start reading further: Keep in mind that this is a work in progress, and although i'm a "Systems&Informatics Engineer" i consider myself a novice programmer. If you have any question you don't see answered here, please leave your comment on the section below to help me improve this article.Also, you should be aware that this post was a result of a lot of work. If you like it, please feel free to show your appreciation by offering me a cup of coffee through paypal and/or add value to this article by sharing your knowledge.
Objectives of this tutorial:
- To establish a standard "archetype" for Document/Wrapped SOAP JAX-WS WebServices development
- To demonstrate how a Maven project can build-deploy-test a Document/Wrapped SOAP WebService
- You have knowledge on Plain Old Java Objects (POJO's) development.
- You understand the concept of WebService, it's benefits and are able to decide when and how it should be used.
- You understand the diference between a RESTful WebService and a SOAP WebService
- You understand the concept of "server side container" and it's purpose.
- You have downloaded and installed the Maven build software, or you are using Maven through the m2e Eclipse plug-in
Conventions over Configuration
First of all, as we will be using the Maven WAR plugin we should be aware of the project's file structure that is required by this plugin (as well as other conventions required by Maven which i won't discuss here)Don't mind the red 'X' over the pom.xml (the m2e was having a bad day).
As you can see from the picture above, all you need to build and deploy a WebService to Tomcat are this 5 files. Be aware that the Maven WAR plugin requires you to have the web.xml and sun-jaxws.xml at that specific location so that they can be included in the deployment to Tomcat. We will talk about each file in the following sections.
What should the pom.xml contain?
The pom.xml is the Maven build file and contains all the required configurations to build, deploy and test the project. In this tutorial, it only contains the configurations that are absolutely essential for this project to pass the tests. I will talk here about the essentials of the configuration that prepare the WAR for proper Tomcat deployment and the Failsafe plug-in.Get ready for WAR
So... What do we need to get started? First of all let's start by calling some friends to the WAR :P We will need some "dependencies" to resolve our classpath needs:1 2 3 4 5 6 7 8 9 | < dependencies > ... < dependency > < groupId >com.sun.xml.ws</ groupId > < artifactId >jaxws-rt</ artifactId > < version >2.1.3</ version > </ dependency > ... </ dependencies > |
Next, we will need to generate a lot of "WebServices related artifacts" based on the Annotations (like @WebService and @WebMethod) present in our WebService class such as WSDL files and other Java classes to handle the WebServices requests:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | < build > < plugins > < plugin > <!-- <groupId>org.codehaus.mojo</groupId> --> < groupId >org.jvnet.jax-ws-commons</ groupId > < artifactId >jaxws-maven-plugin</ artifactId > <!-- <version>1.12</version> --> < version >2.3</ version > < executions > < execution > < id >generate-wsdl</ id > < phase >process-classes</ phase > < goals > < goal >wsgen</ goal > </ goals > < configuration > < sei >ext.domain.pkg.MyService</ sei > <!-- <genWsdl>true</genWsdl> --> </ configuration > </ execution > </ executions > </ plugin > |
Also: Did you noticed the commented Codehaus MOJO plug-in reference? This will also work if uncommented, but will show you a "deprecated warning" at build time.
The WAR builder shall be configured to exclude the web.xml file: To avoid warnings and because a web.xml can already be present at a Tomcat production server.
1 2 3 4 5 6 7 8 9 10 | < build > < plugins > ... < plugin > < artifactId >maven-war-plugin</ artifactId > < version >2.3</ version > < configuration > < packagingExcludes >WEB-INF/web.xml</ packagingExcludes > </ configuration > </ plugin > |
Now that the WAR is built we need to deploy it to a Tomcat container. From what i know, there are maven plugins that enable you to deploy to Jetty, JBoss and also Grizzly(a client side J2EE stack), to do that, just change the following section with the appropriate plugin configuration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | < build > < plugins > ... < plugin > < groupId >org.apache.tomcat.maven</ groupId > < artifactId >tomcat7-maven-plugin</ artifactId > < version >2.1</ version > < configuration > < path >/</ path > </ configuration > < executions > < execution > < id >start-tomcat</ id > < phase >pre-integration-test</ phase > < goals > < goal >run</ goal > </ goals > < configuration > < fork >true</ fork > </ configuration > </ execution > < execution > < id >stop-tomcat</ id > < phase >post-integration-test</ phase > < goals > < goal >shutdown</ goal > </ goals > </ execution > </ executions > </ plugin > |
Now that we have our working server, we need to run the integration tests. For that purpose we shall configure the Failsafe plugin:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | < build > < plugins > ... < plugin > <!-- **/IT*.java, **/*IT.java, and **/*ITCase.java --> < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-failsafe-plugin</ artifactId > < version >2.15</ version > < configuration > < encoding >UTF-8</ encoding > </ configuration > < executions > < execution > < goals > < goal >integration-test</ goal > < goal >verify</ goal > </ goals > </ execution > </ executions > </ plugin > |
The WebService
The web service i used is very simple, just as an hello world service you can see anywhere else. Here is the code:1 2 3 4 5 6 7 8 9 10 | package ext.domain.pkg; import javax.jws.WebMethod; import javax.jws.WebService; @WebService public class MyService { @WebMethod public String myMethod(String name) { return "Hello " + name + "!" ; } } |
The WebService configuration files
I acknowledge that it takes quite a configuration effort to put the WebService "up and running" but this is the last step. Take it easy :)We need two things: To configure the web.xml and the sun-jaxws.xml files.
First things first! The web.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <? xml version = "1.0" encoding = "UTF-8" ?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd"> < web-app > < display-name >MyService</ display-name > < listener > < listener-class >com.sun.xml.ws.transport.http.servlet.WSServletContextListener</ listener-class > </ listener > < servlet > < servlet-name >MyService</ servlet-name > < servlet-class >com.sun.xml.ws.transport.http.servlet.WSServlet</ servlet-class > < load-on-startup >1</ load-on-startup > </ servlet > < servlet-mapping > < servlet-name >MyService</ servlet-name > < url-pattern >/MyService</ url-pattern > </ servlet-mapping > < session-config > < session-timeout >120</ session-timeout > </ session-config > </ web-app > |
First of all, a WebService is not a Servlet. A servlet is the basic "entrance point" for any J2EE application. It's like a Main class for J2SE. Because of that, we must tell Tomcat which Servlet do we want him to invoke at what address. As we didn't develop any, we must use one from another provider :) That is were the com.sun.xml.ws.transport.http.servlet.WSServlet comes along to save our back. Next, we tell him to start that Servlet when someone calls http://****/MyService
Ok, now Tomcat will start the WSServlet, but how will the WSServlet know what WebService to run at that address? We must configure that in the sun-jaxws.xml file:
1 2 3 4 5 6 7 8 9 | <? xml version = "1.0" encoding = "UTF-8" ?> < endpoints version = "2.0" > < endpoint name = "EndpointName" implementation = "ext.domain.pkg.MyService" url-pattern = "/MyService" /> </ endpoints > |
Manually test the WebService deployment
By running 'mvn pre-integration-test', you shall now be able to point your browser at http://localhost:8080/MyService and see the following: You can now go drink a cup of coffe, as you have sucessfuly deployed your very simple WebService using Maven and Tomcat :D Hurray!Integration test programmaticly
As good software developers that we all are :D we should have developed the WebService Integration Tests first :D But as i stated earlier, i'm a n00b :P and left the tests for last. Good thing i'm not on a schedule (or the tests wont ever be done).For testing i used Failsafe, the Surefire counterpart that executes on Maven's integration-test phase.
As the test "script" is a bit more "tricky" (as i didn't wanted it to be even more trikier using pre-generated classes from WSDL, as you would normally use for the general development of WebService clients, since this is a pretty simple WebService), i will explain it step-by-step.
To test this service we need to send a SOAP message like the following to our Endpoint:
1 2 3 4 | <? xml version = "1.0" encoding = "UTF-8" ?> < arg0 >rochajoel</ arg0 > </ ns2:myMethod > |
We can save the code above to a file named justPayload.xml and save it in src/test/resources/justPayload.xml as stated here, and load it using:
1 2 3 4 5 | private Document getMyMethodSOAPRequest() { DocumentBuilder builder = factory.newDocumentBuilder(); InputStream is = getClass().getClassLoader().getResourceAsStream( "justPayload.xml" ); Document newDoc = builder.parse(is); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | private Document getMyMethodSOAPRequest(String name) throws ParserConfigurationException { DocumentBuilderFactory docFactory = DocumentBuilderFactory .newInstance(); DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); // myMethod root node Document doc = docBuilder.newDocument(); "myMethod" ); rootElement.setPrefix( "ns2" ); doc.appendChild(rootElement); if (name == null ) return doc; // arg0 argument node Element arg0 = doc.createElement( "arg0" ); arg0.setTextContent(name); rootElement.appendChild(arg0); return doc; } |
After you've decided which kind of XML loading you prefer, we need to send the SOAP request to our Endpoint located at http://localhost:8080/MyService. To do that we can use the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @Test public void testMyService() throws Exception { URL wsdlURL = new URL(srvAddr + "?wsdl" ); QName serviceName = new QName(srvcNamespace, "MyServiceService" ); QName portName = new QName(srvcNamespace, "MyServicePort" ); String myMethodArg0_Name = "rochajoel" ; Service jaxwsService = Service.create(wsdlURL, serviceName); DOMSource request = new DOMSource(getMyMethodSOAPRequest(myMethodArg0_Name)); Dispatch<Source> disp = jaxwsService.createDispatch( portName, Source. class , Service.Mode.PAYLOAD); Source result = disp.invoke(request); DOMResult domResponse = new DOMResult(); Transformer trans = TransformerFactory.newInstance().newTransformer(); trans.transform(result, domResponse); assertEquals( "unexpected myMethod greeting" , "Hello " + myMethodArg0_Name + "!" , domResponse.getNode().getFirstChild().getTextContent().trim()); } |
That's it! Just run your 'mvn clean verify' and you should be able to "succed your build" with 0 failures.
Happy Coding!