Apache CXF: JAX-RS Restful web service for uploading/downloading Word document + Java client

In this article, we will learn and implement JAX-RS Restful web service which is exposed to upload/download MS Word document

Download

  • Annotate download MS Word document method with @Produces(“application/msword”)
  • Set “Content-Disposition” in the ResponseBuilder to allow downloading users to select path to download MS Word document
  • Finally, build() the ResponseBuilder and return Response object with “download success” message

Upload

  • Annotate upload method with @Consumes(“multipart/form-data”)
  • Get the List<Attachment> and process the attachments to get headers and inputstreams
  • Finally, write the steams to the upload file server using basic file handling operations
  • Return “upload success” message within Response object

Annotation Used

  • @Path (javax.ws.rs.Path)
  • @GET (javax.ws.rs.GET)
  • @POST (javax.ws.rs.POST)
  • @Consumes (javax.ws.rs.Consumes)
  • @Produces (javax.ws.rs.Produces)
  • @Service (org.springframework.stereotype.Service)
  • MediaType (javax.ws.rs.core.MediaType)

Technology Used

  • Java 1.7
  • Eclipse Luna IDE
  • Spring-4.0.0-RELEASE
  • Apache-CXF-3.0.0
  • Apache Maven 3.2.1
  • Apache Tomcat 7.0.54

Mavenize or download required jars

Add Apache-CXF-3.0.0 & Spring-4.0.0-RELEASE dependencies to pom.xml

	<dependencies>
			<!-- Apache CXF -->
			<dependency>
				<groupId>org.apache.cxf</groupId>
				<artifactId>cxf-rt-frontend-jaxrs</artifactId>
				<version>${cxf.version}</version>
			</dependency>
			<dependency>
				<groupId>org.apache.cxf</groupId>
				<artifactId>cxf-rt-transports-http</artifactId>
				<version>${cxf.version}</version>
			</dependency>
			<dependency>
				<groupId>org.apache.cxf</groupId>
				<artifactId>cxf-rt-transports-http-jetty</artifactId>
				<version>${cxf.version}</version>
			</dependency>

			<!-- Apache HTTP components for writing test client -->
			<dependency>
				<groupId>commons-httpclient</groupId>
				<artifactId>commons-httpclient</artifactId>
				<version>3.1</version>
				<scope>compile</scope>
			</dependency>
			<dependency>
				<groupId>org.apache.httpcomponents</groupId>
				<artifactId>httpclient</artifactId>
				<version>${apache.httpcomponents.version}</version>
				<scope>compile</scope>
			</dependency>
			<dependency>
				<groupId>org.apache.httpcomponents</groupId>
				<artifactId>httpcore</artifactId>
				<version>${apache.httpcomponents.version}</version>
				<scope>compile</scope>
			</dependency>
			<dependency>
				<groupId>org.apache.httpcomponents</groupId>
				<artifactId>httpmime</artifactId>
				<version>${apache.httpcomponents.version}</version>
				<scope>compile</scope>
			</dependency>

			<!-- Spring framework -->
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-core</artifactId>
				<version>${spring.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-context</artifactId>
				<version>${spring.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-web</artifactId>
				<version>${spring.version}</version>
			</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 spring site or maven repository and include them in the classpath

Directory Structure

Before moving on, let us understand the directory/package structure once you create project 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
  • src/main/resources –> all 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)

1_ApacheCXF-UP-DOWN-Doc-File_Project_Structure_In_Eclipse

Jars Libraries Used in the Project (Maven Dependencies)

2_ApacheCXF-UP-DOWN-Text-File_Jars_In_Classpath

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-RS Restful web service for uploading/downloading files

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
  • All http requests with URL pattern “/services/*” will be sent to the registered servlet called “CXFServlet” (org.apache.cxf.transport.servlet.CXFServlet)
  • <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 these files
  • <welcome-file-list> files under this tag is the start-up page

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 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">

	<display-name>ApacheCXF-Up-Down-Doc-File</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>

	<!-- web context param -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>WEB-INF/apache-cxf-services.xml</param-value>
	</context-param>

	<!-- listener -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- welcome file list -->
	<welcome-file-list>
		<welcome-file>index.jsp</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. CXFServlet receives the incoming http requests and invokes corresponding registered beans in according to http url path

NOTE: For this Restful JAX-RS application, we are using Spring annotations to define/register beans in the spring container thereby avoiding lot of boilerplate code to write

This apache-cxf-services.xml describes,

  • <jaxrs:server /> defines which service bean to be invoked for the incoming http requests. In this case, any wild card pattern “/” will invoke “fileService” which is registered as service bean using @Service(“fileService”) annotation (on top of the FileServiceImpl java class)
  • <context:annotation-config /> to activate annotation on the registered beans with application context
  • <context:component-scan base-package=”” /> tag scans all classes & sub-classes under the value of base-package attribute and register them with the Spring container
  • NOTE: For two different beans we can have two different url-pattern(address) like
		<jaxrs:server id="restContainer" address="/">
			<jaxrs:serviceBeans>
				<ref bean="fileService" />
			</jaxrs:serviceBeans>
		</jaxrs:server>
		
		<jaxrs:server id="twotest" address="/two">
			<jaxrs:serviceBeans>
				<ref bean="testService" />
			</jaxrs:serviceBeans>
		</jaxrs:server>

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-3.0.xsd
	http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd">

	<!-- to turn on annotation wiring == turns on only the registered beans
		through ApplicationContext -->
	<context:annotation-config />

	<!-- scans and register beans using annotation-config (metadata) -->
	<context:component-scan base-package="com.apache.cxf.upload.download.service" />

	<!-- CXFServlet configured in web.xml sends requests here -->
	<jaxrs:server id="restContainer" address="/">
		<jaxrs:serviceBeans>
			<ref bean="fileService" />
		</jaxrs:serviceBeans>
	</jaxrs:server>
</beans>

Let’s see coding in action

 

URL Pattern

Http URL for any common web application is http://<server>:<port>/<context-root>/<from_here_application_specific_path>

In our example, we are going to deploy war into Tomcat 7.0 server so our server & port are localhost and 8080 respectively. Context root is the project name i.e.; ApacheCXF-UP-DOWN-Doc-File. Initial path for this application is http://localhost:8080/ApacheCXF-UP-DOWN-Doc-File

We have configured “/services/*” as url-pattern for the CXFServlet in web.xml and at interface-level (or say class-level) path configured is “/fileservice” using @Path annotation. Next, respective path for each method annotated with @Path (method-level)

File Service interface

File service exposes two public methods one for downloading MS Word document and another for uploading file

Download MS Word document method annotated with @Produces(“application/msword”) and upload file method annotated with @Consumes(“multipart/form-data”)

NOTE: It’s always a good programming practice to do code-to-interface and have its implementation separately

IFileService.java

package com.apache.cxf.upload.download.service;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.cxf.jaxrs.ext.multipart.Attachment;

@Path("/fileservice")
public interface IFileService {

	// http://localhost:8080/ApacheCXF-UP-DOWN-Doc-File/services/fileservice/download/doc
	@GET
	@Path("/download/doc")
	@Produces("application/msword")
	public Response downloadDocFile();

	// http://localhost:8080/ApacheCXF-UP-DOWN-Doc-File/services/fileservice/upload/doc
	@POST
	@Path("/upload/doc")
	@Consumes(MediaType.MULTIPART_FORM_DATA)
	public Response uploadDocFile(List<Attachment> attachments, @Context HttpServletRequest request);
}

File Service implementation

Implements above interface

Download

It’s quite straightforward, constructing file object supplying path location for the .docx file to be downloaded and setting this file object in ResponseBuilder and returning Response object

Upload

Iterate through each attachments extracting headers & inputstreams and writing to the UPLOAD_FILE_SERVER using basic file handling operations

FileServiceImpl.java

package com.apache.cxf.upload.download.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import javax.activation.DataHandler;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;

import org.apache.cxf.jaxrs.ext.multipart.Attachment;
import org.springframework.stereotype.Service;

@Service("fileService")
public class FileServiceImpl implements IFileService {

	public static final String UPLOAD_FILE_SERVER = "D:\\Demo\\upload\\";

	public Response downloadDocFile() {

		File file = new File("D:/Demo/download/Sample.docx");

		ResponseBuilder responseBuilder = Response.ok((Object) file);
		responseBuilder.header("Content-Disposition", "attachment; filename=\"MyWordDocFile.docx\"");
		return responseBuilder.build();
	}

	public Response uploadDocFile(List<Attachment> attachments, HttpServletRequest request) {

		// local variables
		DataHandler dataHandler = null;
		MultivaluedMap<String, String> multivaluedMap = null;
		String fileName = null;
		InputStream inputStream = null;

		for (Attachment attachment : attachments) {
			dataHandler = attachment.getDataHandler();
			try{
				// get filename to be uploaded
				multivaluedMap = attachment.getHeaders();
				fileName = getFileName(multivaluedMap);

				if(null != fileName && !"".equalsIgnoreCase(fileName)){
					// write & upload file to server
					inputStream = dataHandler.getInputStream();
					writeToFileServer(inputStream, fileName);

					// close the stream
					inputStream.close();
				}
			}
			catch(IOException ioex) {
				ioex.printStackTrace();
			}
			finally {
				// release resources, if any
			}
		}
		return Response.ok("upload success").build();
	}

	/**
	 *
	 * @param inputStream
	 * @param fileName
	 */
	private void writeToFileServer(InputStream inputStream, String fileName) {

		OutputStream outputStream = null;
		try {
			outputStream = new FileOutputStream(new File(UPLOAD_FILE_SERVER + fileName));
			int read = 0;
			byte[] bytes = new byte[1024];
			while ((read = inputStream.read(bytes)) != -1) {
				outputStream.write(bytes, 0, read);
			}
			outputStream.flush();
			outputStream.close();
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
		}
		finally{
			//release resource, if any
		}
	}

	/**
	 *
	 * @param multivaluedMap
	 * @return
	 */
	private String getFileName(MultivaluedMap<String, String> multivaluedMap) {

		String[] contentDisposition = multivaluedMap.getFirst("Content-Disposition").split(";");
		for (String filename : contentDisposition) {

			if ((filename.trim().startsWith("filename"))) {
				String[] name = filename.split("=");
				String exactFileName = name[1].trim().replaceAll("\"", "");
				return exactFileName;
			}
		}
		return "unknownFile";
	}
}

View Technologies

<table> element within <form> tag, on click of “Upload File” button invokes upload file service method which is configured in the action attribute. Maximum of three files can be uploaded and this is supported in the implementation

uploadDocFile.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Apache CXF : File Upload</title>
</head>
<body>
	<center>
		<b>Apache CXF : JAX-RS Restful web service Learning</b>
		<h4>Uploading files using JAX-RS Restful web service</h4>
		<div
			style="width: 400px; border: 1px solid blue; padding: 20px; text-align: center;">

			<form method="post" action="services/fileservice/upload/doc"
				enctype="multipart/form-data">
				<table align="center" border="1" bordercolor="black" cellpadding="0"
					cellspacing="0">
					<tr>
						<td>Select File 1 :</td>
						<td><input type="file" name="uploadedFile1" size="100" /></td>
					</tr>
					<tr>
						<td>Select File 2 :</td>
						<td><input type="file" name="uploadedFile2" size="100" /></td>
					</tr>
					<tr>
						<td>Select File 3 :</td>
						<td><input type="file" name="uploadedFile3" size="100" /></td>
					</tr>
					<tr>
						<td><input type="submit" value="Upload File" /></td>
						<td><input type="reset" value="Reset" /></td>
					</tr>
				</table>
			</form>
		</div>
	</center>
</body>
</html>

 

Deployment

  • Run maven command to build the war : mvn clean install (use command prompt or integrated maven in eclipse IDE
  • Copy war file from target folder
  • Paste it into apache tomcat (webapps folder)
  • Start the tomcat server

Test the service !!

 

Testing

There are many ways to do testing

  • JSP client
  • Copy URL of GET service into web browser
  • Advanced REST client from Google Chrome
  • Rest client from Mozilla Firefox Add On
  • Write your own client for example, Java client using improved CloseableHttpClient from Apache
  • JDK’s in-built classes like HttpURLConnection

 

1. JSP client

 

1.1 upload file using JSP client

Enter URL: http://localhost:8080/ApacheCXF-UP-DOWN-Doc-File/uploadDocFile.html
And select .docx file using file chooser
Click “Upload File”
3_ApacheCXF-UP-DOWN-Doc-File_Jsp_client_upload_doc

 

Result in web browser: upload success

4_ApacheCXF-UP-DOWN-Doc-File_Jsp_client_upload_doc_success

 

1.2 download text file

Enter URL: http://localhost:8080/ApacheCXF-UP-DOWN-Doc-File/services/fileservice/download/doc into web browser
Allows downloading users to open/save .docx file
5_ApacheCXF-UP-DOWN-Doc-File_download_firefox_doc

 

2. Java Client

 

2.1 Upload MS Word document

Uses Apache’s improved CloseableHttpClient & MultipartEntityBuilder for invoking Restful web service to upload .docx file

TestUploadFileService.java

package test.apache.cxf.rest.service;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class TestUploadFileService {

	public static void main(String []args) {

		String httpURL = "http://localhost:8080/ApacheCXF-UP-DOWN-Doc-File/services/fileservice/upload/doc";
		File filePath = new File("D:/Demo/download/Sample.docx");
		String responseString = testUploadService(httpURL, filePath);
		System.out.println("responseString : " + responseString);
	}

	@SuppressWarnings("deprecation")
	public static String testUploadService(String uri, File filePath) {

		// local variables
		HttpPost httpPost = null;
		CloseableHttpClient closeableHttpClient = null;
		HttpResponse httpResponse = null;
		MultipartEntityBuilder multipartEntityBuilder = null;
		HttpEntity httpEntity = null;
		FileBody fileBody = null;
		InputStream inputStream = null;
		BufferedReader bufferedReader = null;
		StringBuilder stringBuilder = null;

		try {
			// http post request header
			httpPost = new HttpPost(uri);

			// constructs file to be uploaded
			fileBody = new FileBody(filePath);
			multipartEntityBuilder = MultipartEntityBuilder.create();
			multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
			multipartEntityBuilder.addPart("uploadfile", fileBody);
			httpEntity = multipartEntityBuilder.build();
			httpPost.setEntity(httpEntity);

			// actual execution of http post request
			closeableHttpClient = HttpClients.createDefault();
			httpResponse = closeableHttpClient.execute(httpPost);

			System.out.println("Response code/message: " + httpResponse.getStatusLine());
			httpEntity = httpResponse.getEntity();

			// get the response content
			inputStream = httpEntity.getContent();
			bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
			stringBuilder = new StringBuilder();
			String strReadLine = bufferedReader.readLine();

			// iterate to get the data and append in StringBuilder
			while (strReadLine != null) {
				stringBuilder.append(strReadLine);
				strReadLine = bufferedReader.readLine();
				if (strReadLine != null) {
					stringBuilder.append("\n");
				}
			}
		}
		catch (UnsupportedEncodingException usee) {
			usee.printStackTrace();
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
		finally {
			// shuts down, when work done
			closeableHttpClient.getConnectionManager().closeExpiredConnections();
		}
		return stringBuilder.toString();
	}
}

Output in Console (upload .docx file)

Response code/message: HTTP/1.1 200 OK
responseString : upload success

2.2 Download MS Word document

Uses JDK’s in-built classes like HttpURLConnection to invoke Restful web service to download .docx file

TestDownloadFileService.java

package test.apache.cxf.rest.service;

import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.ws.rs.HttpMethod;

public class TestDownloadFileService {

	public static final String DOWNLOAD_FILE_LOCATION = "D:/Demo/test/";

	public static void main(String []args) {

		String httpURL = "http://localhost:8080/ApacheCXF-UP-DOWN-Doc-File/services/fileservice/download/doc";
		String responseString = testDownloadService(httpURL);
		System.out.println("responseString : " + responseString);
	}

	/**
	 * This method uses HttpURLConnection to invoke exposed Restful web service and returns the response string to the calling client
	 * @param requestParams
	 * @return
	 */
	public static String testDownloadService(String httpUrl) {

		// local variables
		URL url = null;
		HttpURLConnection httpURLConnection = null;
		String responseMessageFromServer = null;
		String responseString = null;
		InputStream inputStream = null;
		OutputStream outputStream = null;

		try {   

			// httpURLConnection
			url = new URL(httpUrl);
			httpURLConnection = (HttpURLConnection) url.openConnection();
			httpURLConnection.setRequestMethod(HttpMethod.GET);
			httpURLConnection.setRequestProperty("Accept", "application/msword");
			httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			httpURLConnection.setDoOutput(true);

			System.out.println("Response code: " + httpURLConnection.getResponseCode());
			if (httpURLConnection.getResponseCode() == 200) {

				responseMessageFromServer = httpURLConnection.getResponseMessage();
				System.out.println("ResponseMessageFromServer: " + responseMessageFromServer);

				inputStream = httpURLConnection.getInputStream();
				outputStream = new FileOutputStream(DOWNLOAD_FILE_LOCATION + "MyDocFile.docx");
				byte[] buffer = new byte[1024];
				int bytesRead;
				while ((bytesRead = inputStream.read(buffer)) != -1) {
					outputStream.write(buffer, 0, bytesRead);
				}
				responseString =  outputStream.toString();
				outputStream.close();

				responseString = "download success";
			}
			else {
				// do some actions
			}
		}
		catch(Exception  ex){
			ex.printStackTrace();
		}
		finally{
			httpURLConnection.disconnect();
		}
		return responseString;
	}
}

Output in Console (download .docx file)

Response code: 200
ResponseMessageFromServer: OK
responseString : download success

Conclusion: It’s quite easy to implement download/upload files functionality in JAX-RS Restful web service using simple annotations like @Produces & @Consumes on top of the service class to be exposed

In the next article, very similarly we will implement an example for uploading/downloading zipped (.zip) files

Download project

ApacheCXF-Uploading-Downloading-Doc-File (6kB)

Happy Coding !!
Happy Learning !!

Apache CXF: JAX-RS Restful web service for uploading/downloading Zip file + Java client
Apache CXF: JAX-RS Restful web service for uploading/downloading Excel file + Java client