In this article, we will learn and implement a JAX-WS based SOAP Web Service using Apache CXF Reference Implementation (Apache-CXF-RI)
Apache CXF has a good integration with Spring framework, so developers can define beans using spring annotation like @Service, @Repository & @Component and later this can be invoked for the incoming http/https SOAP requests based on the CXFServlet configured in the web application
Basically, we got two options while developing/implementation of SOAP Web Service
- Top-Down approach or Contract first
- Bottom-UP approach or Code first
We will concentrate on the second approach i.e.; Bottom-UP approach. Often developer community finds developing SOAP based Web Service using bottom-up lot easier comparing the former contract based approach
It’s considered a standard approach to design WSDL contract document collaborating with architects, designer and governance team in a large enterprise application, where possibly there could be multiple interactions amongst exposed services. Anyways, it is purely design choice
In this approach, java implementation class is coded first and using maven jaxws plugin (java2ws/wsgen goals) we can generate required artifacts viz.; XML Schema & WSDL
Note: For simple use case, we can use command line interface to generate java artifacts
Technology Used
- Java 1.7
- Eclipse Luna IDE
- Apache CXF-3.0.2
- Spring-4.1.0.RELEASE
- Apache Maven 3.2.1
- Apache Tomcat-8.0.15
- Oracle Weblogic server 12c
Mavenize or download required jars
Add apache-cxf-3.0.2 & spring-4.1.0.RELEASE dependencies to pom.xml
<!-- properties --> <properties> <cxf.version>3.0.2</cxf.version> <spring.version>4.1.0.RELEASE</spring.version> <cxf.scope>compile</cxf.scope> <compileSource>1.7</compileSource> <maven.compiler.target>1.7</maven.compiler.target> <maven.compiler.source>1.7</maven.compiler.source> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- apache cxf jax-ws 3.0.2 --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${cxf.version}</version> <scope>${cxf.scope}</scope> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf.version}</version> <scope>${cxf.scope}</scope> </dependency> <!-- spring framework 4.1.0 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> <scope>compile</scope> </dependency> </dependencies>
Folks who aren’t familiar with Maven concepts or don’t require maven for their project, can download the below jars individually from the central repository or maven repository or maven2 and include them in the classpath
- cxf-core-3.0.2
- cxf-rt-bindings-soap-3.0.2
- cxf-rt-bindings-xml-3.0.2
- cxf-rt-databinding-jaxb-3.0.2
- cxf-rt-frontend-jaxws-3.0.2
- cxf-rt-frontend-simple-3.0.2
- cxf-rt-transports-http-3.0.2
- cxf-rt-ws-addr-3.0.2
- cxf-rt-ws-policy-3.0.2
- cxf-rt-wsdl-3.0.2
- jaxb-core-2.2.10
- jaxb-impl-2.2.10
- neethi-3.0.3
- stax2-api-3.1.4
- woodstox-core-asl-4.4.1
- wsdl4j-1.6.3
- xml-resolver-1.2
- xmlschema-core-2.1.0
- aopalliance-1.0
- asm-3.3.1
- spring-aop-4.1.0.RELEASE
- spring-beans-4.1.0.RELEASE
- spring-context-4.1.0.RELEASE
- spring-core-4.1.0.RELEASE
- spring-expression-4.1.0.RELEASE
- spring-web-4.1.0.RELEASE
Steps to generate required artifacts from SEI (interface/class)
- code java interface and class implementing this interface
- configure maven plugins (wsgen/java2ws goal) in pom.xml with correct and complete path of SEI i.e.; service endpoint interface
- Run maven command “mvn generate-sources” from project’s context-root
Let us understand above steps in more detail
Code Java Interface/Class
Book Service Interface
This interface defines a single method called “getBookNameBasedOnISBN” which inputs string argument and returns a single string response based on the request ISBN number
IBookService.java
package com.jaxws.series.bottom.up.approach.service; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; import javax.jws.soap.SOAPBinding.Style; import javax.jws.soap.SOAPBinding.Use; @WebService(name="BookService") @SOAPBinding(style = Style.DOCUMENT, use = Use.LITERAL) public interface IBookService { public String getBookNameBasedOnISBN(String isbnNumber); }
Book Service Implementation
This service class implements above interface and provides concrete implementation, which on valid ISBN request number returns a book name otherwise returns a simple string with invalid message i.e.; “INVALID_ISBN_NUMBER”
BookServiceImpl.java
package com.jaxws.series.bottom.up.approach.service; import javax.jws.WebService; @WebService(serviceName="BookService", endpointInterface="com.jaxws.series.bottom.up.approach.service.IBookService") public class BookServiceImpl implements IBookService { public String getBookNameBasedOnISBN(String isbnNumber) { if(isbnNumber.equalsIgnoreCase("ISBN-2134")){ return "Microbiology"; } return "Invalid_ISBN_Number"; } }
Configure maven plugin in pom.xml (java2ws goal)
This plugin which defines java2ws goal from cxf-java2ws-plugin generates xsd/wsdl artifacts from SEI i.e.; Service Endpoint Interface under the folder location specified with attributes viz.; outputFile
<!-- plugin 3- apache cxf codegen wsdl2java goal --> <plugin> <groupId>org.apache.cxf</groupId> <artifactId>cxf-java2ws-plugin</artifactId> <version>${cxf.version}</version> <executions> <execution> <id>generate-wsdl</id> <phase>process-classes</phase> <configuration> <className>com.jaxws.series.bottom.up.approach.service.BookServiceImpl</className> <serviceName>BookService</serviceName> <address>http://localhost:8080/ApacheCXF-JAX-WS-Bottom-Up/services/BookService</address> <outputFile>${basedir}\generated\resources\wsdl\BookService.wsdl</outputFile> <argline>-createxsdimports</argline> <genWsdl>true</genWsdl> <verbose>true</verbose> <skip>false</skip> <genWrapperbean>false</genWrapperbean> <genClient>false</genClient> <genServer>false</genServer> </configuration> <goals> <goal>java2ws</goal> </goals> </execution> </executions> </plugin>
Run “mvn generate-sources”
Look at the generated java source files in the generated folder
After running above maven command, you will get to see below generated java files
- BookService.wsdl
- BookService_schema1.xsd
BookService.wsdl
<?xml version="1.0" encoding="UTF-8"?> <wsdl:definitions name="BookService" targetNamespace="http://service.approach.up.bottom.series.jaxws.com/" xmlns:tns="http://service.approach.up.bottom.series.jaxws.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"> <wsdl:types> <schema xmlns="http://www.w3.org/2001/XMLSchema"> <import namespace="http://service.approach.up.bottom.series.jaxws.com/" schemaLocation="BookService_schema1.xsd" /> </schema> </wsdl:types> <wsdl:message name="getBookNameBasedOnISBN"> <wsdl:part name="parameters" element="tns:getBookNameBasedOnISBN"> </wsdl:part> </wsdl:message> <wsdl:message name="getBookNameBasedOnISBNResponse"> <wsdl:part name="parameters" element="tns:getBookNameBasedOnISBNResponse"> </wsdl:part> </wsdl:message> <wsdl:portType name="BookService"> <wsdl:operation name="getBookNameBasedOnISBN"> <wsdl:input name="getBookNameBasedOnISBN" message="tns:getBookNameBasedOnISBN"> </wsdl:input> <wsdl:output name="getBookNameBasedOnISBNResponse" message="tns:getBookNameBasedOnISBNResponse"> </wsdl:output> </wsdl:operation> </wsdl:portType> <wsdl:binding name="BookServiceSoapBinding" type="tns:BookService"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="getBookNameBasedOnISBN"> <soap:operation soapAction="" style="document" /> <wsdl:input name="getBookNameBasedOnISBN"> <soap:body use="literal" /> </wsdl:input> <wsdl:output name="getBookNameBasedOnISBNResponse"> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="BookService"> <wsdl:port name="BookServiceImplPort" binding="tns:BookServiceSoapBinding"> <soap:address location="http://localhost:8080/ApacheCXF-JAX-WS-Bottom-Up/services/BookService" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
BookService_schema1.xsd
<xs:schema xmlns:tns="http://service.approach.up.bottom.series.jaxws.com/" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="unqualified" targetNamespace="http://service.approach.up.bottom.series.jaxws.com/" version="1.0"> <xs:element name="getBookNameBasedOnISBN" type="tns:getBookNameBasedOnISBN" /> <xs:element name="getBookNameBasedOnISBNResponse" type="tns:getBookNameBasedOnISBNResponse" /> <xs:complexType name="getBookNameBasedOnISBN"> <xs:sequence> <xs:element minOccurs="0" name="arg0" type="xs:string" /> </xs:sequence> </xs:complexType> <xs:complexType name="getBookNameBasedOnISBNResponse"> <xs:sequence> <xs:element minOccurs="0" name="return" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:schema>
Directory Structure
Before moving on, let us understand the directory/package structure once you create project and/plus after generating java artifacts in Eclipse IDE
Maven has to follow certain directory structure
- src/test/java –> test related files, mostly JUnit test cases
- src/main/java –> create java source files under this folder
- generated/java/source –> generated java source files are placed here
- src/main/resources –> all configuration files placed here
- src/test/resources –> all test related configuration files placed here
- Maven Dependencies or Referenced Libraries –> includes jars in the classpath
- WEB-INF under webapp –> stores web.xml & other configuration files related to web application
Project Structure (Package Explorer view in Eclipse)
Jar Libraries Used in the Project (Maven Dependencies)
Web application
For any web application, entry point is web.xml which describes how the incoming http requests are served / processed. Further, it describes about the global-context and local-context param (i.e.; <context-param> & <init-param>) for loading files particular to project requirements & contains respective listener
With this introduction, we will understand how we configured web.xml for Apache CXF JAX-WS SOAP based Web Service
web.xml (the entry point –> under WEB-INF)
This web.xml file describes,
- Like any JEE web framework register “org.apache.cxf.transport.servlet.CXFServlet” with servlet container
- http requests with URL pattern “/services/*” will be sent to the registered servlet called “CXFServlet” i.e.; (org.apache.cxf.transport.servlet.CXFServlet)
- configure spring context loader listener for loading spring context files “org.springframework.web.context.ContextLoaderListener”
- <context-param> with its attributes describes the location of the “apache-cxf-service.xml” file from where it has to be loaded. We will discuss briefly about this file
- configure session timeout in secs using <session-config> tag
- <welcome-file-list> files under this tag is the start-up page
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" 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_3_0.xsd"> <display-name>ApacheCXF-JAX-WS-Bottom-UP</display-name> <!-- Apache CXF --> <servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> <!-- listener --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- web context param --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/apache-cxf-services.xml</param-value> </context-param> <!-- session timeout --> <session-config> <session-timeout>60</session-timeout> </session-config> <!-- welcome file list --> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>
Apache CXF services
Apache CXF comes with spring based configuration, so it is easy to register beans in the spring container much like we do any bean in spring application in addition to configuring JAX-WS endpoints and interceptors, etc
In CXF endpoint, we can define implementor i.e.; Java endpoint implementation class and address. So, incoming requests from “CXFServlet” servlet invokes corresponding implementation class with configured address-pattern
For more JAX-WS element details see here
This apache-cxf-services.xml describes,
- < jaxws:endpoint /> defines which service implementation class to be invoked for the incoming http/https SOAP requests with address-pattern configured
NOTE: For two different beans we can have two different url-pattern (address) like
<jaxws:endpoint id="bookservice" implementor="com.jaxws.series.top.down.approach.service.BookServiceImpl" address="/book"> </jaxws:endpoint> <jaxws:endpoint id="otherservice" implementor="other.qualified.package.name" address="/other"> </jaxws:endpoint>
apache-cxf-services.xml
<?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:jaxws="http://cxf.apache.org/jaxws" xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <jaxws:endpoint id="bookservice" implementor="com.jaxws.series.bottom.up.approach.service.BookServiceImpl" address="/book"> </jaxws:endpoint> </beans>
URL Pattern
Http url for any common web application is http://<server>:<port>/<root-context>/<from_here_application_specific_path>
In our example, we are going to deploy the war into Tomcat 8.0 server so our server and port are localhost and 8080 respectively. Context root is the project name i.e.; ApacheCXF-JAX-WS-Bottom-UP. Initial path for this application is http://localhost:8080/ApacheCXF-JAX-WS-Bottom-UP
We have configured “/services/*” as url-pattern for the “CXFServlet” servlet in web.xml and our business implementation class implements the portType interface generated from WSDL file which is annotated with @WebService annotation at class-level
And address configured in the apache-cxf-services.xml is “/book” so final endpoint url is http://localhost:8080/ApacheCXF-JAX-WS-Bottom-UP/services/book/BookService?wsdl
That’s all with coding and configuration part, now let us move on to deployment and testing
Apache Tomcat-8.0.15 Deployment
- Run maven command to build the war: mvn clean install (use command prompt or integrated maven in eclipse IDE
- Copy(ctrl+c) the war file from the target folder
- Paste(ctrl+v) it into apache tomcat (webapps folder)
- Start the tomcat server (Tomcat_Home\bin\startup.bat)
Oracle Weblogic server 12.1.1.0 Deployment
Apache CXF based JAX-WS web service can’t be deployed directly into Oracle WebLogic server as WAR file à it’s a two step process. First build a WAR file and then package this WAR file into an EAR file à Deploy EAR file into weblogic server
See this article for explanation: Packaging WAR as EAR
Steps to be followed
- Run maven command to build the war: mvn clean install (use command prompt or integrated maven in eclipse IDE)
- Once you see “BUILD SUCCESS” after running maven command, it means your war file is successfully built and installed in the local maven repository
- Package this WAR file into an EAR file as explained in this article
- Start weblogic 12c application server and hit the URL http://localhost:7001/console in any of the latest web browser and enter username/password you configured while setting up weblogic 12c server
- Go to Deployments –> click install button –> browse through EAR file location –> say Next –> say Next –> Finish
For Oracle WebLogic 12c server Installation steps see here
Test the service !!
Testing
There are many ways to do testing
- SOAP UI Client
- Java Client using JDK’s in-built classes like HttpURLConnection
- Java Client using SOAP API
- Eclipse’s Web Services Explorer
- Write your own client for example, Java client using httpcomponents from Apache
We will cover first 2 ways of testing above JAX-WS deployed service
1. SOAP UI Client
Load the endpoint URL in SOAP UI Client, which will pre-populate the request XML based on the operation deployed/exposed using WSDL
For example, http://localhost:8080/ApacheCXF-JAX-WS-Bottom-UP/services/book/BookService?wsdl
Request XML
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://service.approach.up.bottom.series.jaxws.com/"> <soapenv:Header /> <soapenv:Body> <ser:getBookNameBasedOnISBN> <!--Optional: --> <arg0>ISBN-2134</arg0> </ser:getBookNameBasedOnISBN> </soapenv:Body> </soapenv:Envelope>
Response XML
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <ns2:getBookNameBasedOnISBNResponse xmlns:ns2="http://service.approach.up.bottom.series.jaxws.com/"> <return>Microbiology</return> </ns2:getBookNameBasedOnISBNResponse> </soap:Body> </soap:Envelope>
2. Java client
For this java client to work/execute, we don’t need to add any extra jars or any new dependency in pom.xml as these classes comes shipped along with JDK. Observe, import statements closely for this client
Note: Request XML pattern formed taking help from pre-populated request XML from SOAP UI client as explained above
TestBookService.java
package test.jaxws.series.bottom.up.approach.service; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.URL; public class TestBookService { /** * wsimport - JAX-WS top-down web service approach * main() method to test/start soap web service * @param args * @throws IOException */ public static void main(String[] args) throws IOException { String httpRequestURL = "http://localhost:8080/ApacheCXF-JAX-WS-Bottom-Up/services/book/BookService?wsdl"; String requestXmlParam = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ser=\"http://service.approach.up.bottom.series.jaxws.com/\">" + " <soapenv:Header/>" + "<soapenv:Body>" + "<ser:getBookNameBasedOnISBN>" + "<arg0>ISBN-2134</arg0>" + " </ser:getBookNameBasedOnISBN>" + "</soapenv:Body>" + "</soapenv:Envelope>"; String responseString = testBookService(httpRequestURL, requestXmlParam); System.out.println("Response String : " + responseString); } /** * This method uses HttpURLConnection to invoke exposed SOAP web service and returns the response string to the calling client * * @param httpRequestURL * @param requestXmlParam * @return responseXML * @throws IOException */ public static String testBookService(String httpRequestURL, String requestXmlParam) throws IOException { // local variables URL url = null; HttpURLConnection httpURLConnection = null; OutputStreamWriter outputStreamWriter = null; String responseMessageFromServer = null; String responseXML = null; try { // set basic request parameters url = new URL(httpRequestURL); httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setDoOutput(true); httpURLConnection.setRequestMethod("POST"); // httpURLConnection.setRequestProperty("SOAPAction", ""); httpURLConnection.setRequestProperty("Content-Type", "text/xml"); httpURLConnection.setRequestProperty("Accept", "application/xml"); // write request XML to the HTTP request outputStreamWriter = new OutputStreamWriter(httpURLConnection.getOutputStream()); outputStreamWriter.write(requestXmlParam); outputStreamWriter.flush(); System.out.println("Response code: " + httpURLConnection.getResponseCode()); if (httpURLConnection.getResponseCode() == 200) { responseMessageFromServer = httpURLConnection.getResponseMessage(); System.out.println("ResponseMessageFromServer: " + responseMessageFromServer); responseXML = getResponseXML(httpURLConnection); } } catch (IOException ioex) { ioex.printStackTrace(); throw ioex; } finally{ // finally close all operations outputStreamWriter.close(); httpURLConnection.disconnect(); } return responseXML; } /** * This method is used to get response XML from the HTTP POST request * * @param httpURLConnection * @return stringBuffer.toString() * @throws IOException */ private static String getResponseXML(HttpURLConnection httpURLConnection) throws IOException { // local variables StringBuffer stringBuffer = new StringBuffer(); BufferedReader bufferedReader = null; InputStreamReader inputStreamReader = null; String readSingleLine = null; try{ // read the response stream AND buffer the result into a StringBuffer inputStreamReader = new InputStreamReader(httpURLConnection.getInputStream()); bufferedReader = new BufferedReader(inputStreamReader); // reading the XML response content line BY line while ((readSingleLine = bufferedReader.readLine()) != null) { stringBuffer.append(readSingleLine); } } catch (IOException ioex) { ioex.printStackTrace(); throw ioex; } finally{ // finally close all operations bufferedReader.close(); httpURLConnection.disconnect(); } return stringBuffer.toString(); } }
Output in console
Response code: 200 ResponseMessageFromServer: OK Response String : <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <ns2:getBookNameBasedOnISBNResponse xmlns:ns2="http://service.approach.up.bottom.series.jaxws.com/"> <return>Microbiology</return> </ns2:getBookNameBasedOnISBNResponse> </soap:Body> </soap:Envelope>
Conclusion: Thus, we have implemented & understood SOAP based Web Service implementation using Apache CXF reference implementation
In the next article, we will understand how we can integrate UsernameToken security headers in Apache CXF JAX-WS reference implementation
Download project
ApacheCXF-JAX-WS-Bottom-Up (10kB)
Happy Coding !!
Happy Learning !!