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
- cxf-rt-frontend-jaxrs
- cxf-rt-transports-http
- cxf-rt-transports-http-jetty
- spring-core-4.0.0-RELEASE
- spring-context-4.0.0-RELEASE
- spring-beans-4.0.0-RELEASE
- spring-aop-4.0.0-RELEASE
- spring-expression-4.0.0-RELEASE
- spring-web-4.0.0-RELEASE
- spring-webmvc-4.0.0-RELEASE
- commons-logging-1.1.1
- aopalliance-1.0
- commons-httpclient
- httpclient
- httpcore
- httpmime
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)
Jars 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-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”
Result in web browser: upload 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
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 !!