Content negotiation in Spring MVC allows you to view data in different format based on the request media type. Here we will see how to view the same data in different media types “json, xml, pdf & excel”.
Objectives:
- How to configure spring to respond to different media type request?
- How to return data in json, xml, pdf or excel format?
- How to write PDF view using iText & Excel view using Apache POI for Excel 2007+?
Environment & Tools:
- Eclipse
- Maven
- Jetty (or any other server)
Libraries:
- Spring framework
- Jackson “JSON processor library”
- iText (5.x) “PDF library”
- Apache POI (3.9) “Excel library”
- Castor “XML databinding library”
About This Sample:
- Client will send GET request to view data “list of articles” in one of the available formats “mime type” (json, xml, pdf or excel).
- The request is mapped to Spring MVC controller which will respond to the client with the data in requested format.
- Spring is configured to detect the requested content type and prepare data in required format accordingly.
- Formatting data in JSON and XML is straight forward no coding is needed if the default output is meeting our requirement.
- However formatting the data in PDF or Excel needs some coding to prepare the layout of these documents. For PDF we will be using iText and for Excel we will be using Apache POI
- The data source in this example “list of articles” is an XML file “articles.xml” which will be converted into Java object “LinkedList<Article>” using Castor XML mapping before responding to the client. The data source can be DB, text file, or just hard coded data.
( 1 ) Project Structure
( 2 ) Data Model & Source
- src/main/java/com/hmkcode/vo/Article.java
This is a class is data model that will hold our data
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package com.hmkcode.vo; import java.util.LinkedList; import java.util.List; public class Article { private String title; private String url; private List<String> categories; private List<String> tags; //getters & setters... } |
- articles.xml
This XML file is just the source of data to be sent to the client. The data source can be also database, text file or hard-coded data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <? xml version = "1.0" encoding = "UTF-8" ?> < linked-list > < article xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:type = "java:com.hmkcode.vo.Article" > < categories xsi:type = "java:java.lang.String" >Spring</ categories > < tags xsi:type = "java:java.lang.String" >Spring</ tags > < tags xsi:type = "java:java.lang.String" >JSON</ tags > < tags xsi:type = "java:java.lang.String" >XML</ tags > < tags xsi:type = "java:java.lang.String" >iText</ tags > < tags xsi:type = "java:java.lang.String" >Apache POI</ tags > < title > Spring MVC View (JSON, XML, PDF or Excel) </ title > </ article > ..... ..... </ linked-list > |
( 3 ) Web & Spring Configuration
- Controller.java
- rest-servlet.xml
- web.xml
- index.html
- /src/main/java/com/hmkcode/controllers/Controller.java
This class is the controller that will receive client request. Client can call this controller by send GET request to /rest/controller/get URL.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | package com.hmkcode.controllers; import java.io.FileNotFoundException; import java.io.FileReader; import java.util.LinkedList; import org.exolab.castor.xml.MarshalException; import org.exolab.castor.xml.Unmarshaller; import org.exolab.castor.xml.ValidationException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.hmkcode.vo.Article; @Controller @RequestMapping ( "/controller" ) public class MyController { public MyController(){ System.out.println( "Init MyController" ); } @RequestMapping (value = "/get" , method = RequestMethod.GET) public LinkedList<Article> get(Model model) { FileReader reader; LinkedList<Article> articles = null ; try { reader = new FileReader( "articles.xml" ); //convert "unmarshal" data from XML "articles.xml" to Java object LinkedList<Article> articles = (LinkedList) Unmarshaller.unmarshal(LinkedList. class , reader); model.addAttribute( "articles" ,articles); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (MarshalException e) { e.printStackTrace(); } catch (ValidationException e) { e.printStackTrace(); } return articles; } } |
- /src/main/webapp/WEB-INF/rest-servlet.xml
In this file we configure spring to handle the request of different media type by setting upContentNegotiatingViewResolver bean. Two properties of ContentNegotiatingViewResolver need to be set “mediaTypes” & “defaultViews“. The first property “mediaTypes” will contain a map of accepted types, the key of this map is the extension that will be passed with request e.g. /rest/controller/get.xlsx while the value is the media type standard name e.g. application/json. The second “defaultViews” list the beans that will generate the data in the requested format.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | xmlns:mvc = "http://www.springframework.org/schema/mvc" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" < context:component-scan base-package = "com.hmkcode.controllers" /> < mvc:annotation-driven /> < bean class = "org.springframework.web.servlet.view.ContentNegotiatingViewResolver" > < property name = "order" value = "1" /> < property name = "mediaTypes" > < map > < entry key = "json" value = "application/json" /> < entry key = "xml" value = "application/xml" /> < entry key = "pdf" value = "application/pdf" /> < entry key = "xlsx" value = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" /> </ map > </ property > < property name = "defaultViews" > < list > <!-- JSON View --> < bean class = "org.springframework.web.servlet.view.json.MappingJacksonJsonView" > </ bean > <!-- XML view --> < bean class = "org.springframework.web.servlet.view.xml.MarshallingView" > < constructor-arg > < bean class = "org.springframework.oxm.castor.CastorMarshaller" > </ bean > </ constructor-arg > </ bean > <!-- PDF view --> < bean class = "com.hmkcode.view.PDFView" > </ bean > <!-- XLSX "Excel" view --> < bean class = "com.hmkcode.view.ExcelView" > </ bean > </ list > </ property > < property name = "ignoreAcceptHeader" value = "true" /> </ bean > </ beans > |
- /src/main/webapp/WEB-INF/web.xml
This is the standard web.xml to define our servlets. Here we define the URL pattern of the requests to be passed to Spring DispatcherServlet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <? xml version = "1.0" encoding = "UTF-8" ?> < web-app xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns = "http://java.sun.com/xml/ns/javaee" xmlns:web = "http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id = "WebApp_ID" version = "2.5" > < display-name >SpringMVC</ display-name > < welcome-file-list > < welcome-file >index.html</ welcome-file > </ welcome-file-list > < servlet > < servlet-name >rest</ servlet-name > < servlet-class >org.springframework.web.servlet.DispatcherServlet</ servlet-class > </ servlet > < servlet-mapping > < servlet-name >rest</ servlet-name > < url-pattern >/rest/*</ url-pattern > </ servlet-mapping > </ web-app > |
- /src/main/webapp/index.html
Just a simple file to display the first page of our web app. Twitter bootstrap is used just for good looking design!.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!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 >Spring MVC - View (JSON, XML, PDF, Excel)</ title > < link href = "bootstrap/css/bootstrap.min.css" rel = "stylesheet" > </ head > < body > < h1 >Spring MVC - View (JSON, XML, PDF, Excel)</ h1 > < div style = "margin:10px;width:700px;text-align:center" > < a href = "/spring-mvc-json-pdf-xls/rest/controller/get.json" class = "label label-info" >JSON</ a > < a href = "/spring-mvc-json-pdf-xls/rest/controller/get.xml" class = "label label-info" >XML</ a > < a href = "/spring-mvc-json-pdf-xls/rest/controller/get.pdf" class = "label label-info" >PDF</ a > < a href = "/spring-mvc-json-pdf-xls/rest/controller/get.xlsx" class = "label label-info" >Excel</ a > </ div > </ body > </ html > |
( 4 ) Views PDF & Excel
- AbstractPdfView.java
- AbstractExcelView.java
- PdfView.java
- ExcelView.java
Spring provides an abstract classorg.springframework.web.servlet.view.document.AbstractPdfView that we can extends to write our PDF view implementation. This class supports only old version of iText “Bruno Lowagie’siText“. If we would like to use the newer version of iText then we need to rewriteAbstractPdfView to be compatible with iText (5.x).
Spring provides abstract classorg.springframework.web.servlet.view.document.AbstractExcelView that we can extends to write our Excel view implementation. This class support only Excel 97 – 2007 or “.xls” file. If we want to create Excel 2007+ or “.xlsx” document we need to rewrite AbstractExcelView to be compatible with POI-XSSF.
- /src/main/java/com/hmkcode/view/PDFView.java
This is class create our PDF document using iText (5.4.2)
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | package com.hmkcode.view; import java.util.LinkedList; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.hmkcode.view.abstractview.AbstractPdfView; import com.hmkcode.vo.Article; import com.itextpdf.text.Anchor; import com.itextpdf.text.BaseColor; import com.itextpdf.text.Chunk; import com.itextpdf.text.Document; import com.itextpdf.text.Font; import com.itextpdf.text.Font.FontFamily; import com.itextpdf.text.pdf.PdfWriter; public class PDFView extends AbstractPdfView { protected void buildPdfDocument( Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest req, HttpServletResponse resp) throws Exception { // Get data "articles" from model @SuppressWarnings ( "unchecked" ) LinkedList<Article> articles = (LinkedList<Article>) model.get( "articles" ); // Fonts Font fontTitle = new Font(FontFamily.TIMES_ROMAN, 14 , Font.BOLD, BaseColor.BLACK); Font fontTag = new Font(FontFamily.HELVETICA, 10 , Font.BOLD, BaseColor.WHITE); for (Article article:articles){ // 1.Title document.add( new Chunk( "Title: " )); Chunk title = new Chunk(article.getTitle(), fontTitle); document.add(title); document.add( new Chunk( " " )); // -- newline document.add(Chunk.NEWLINE); // 2.URL document.add( new Chunk( "URL: " )); Anchor url = new Anchor(article.getUrl()); url.setName(article.getUrl()); url.setReference(article.getUrl()); document.add(url); // -- newline document.add(Chunk.NEWLINE); // 3.Categories Chunk cat = null ; document.add( new Chunk( "Category: " )); for (String category:article.getCategories()){ cat = new Chunk(category, fontTag); cat.setBackground( new BaseColor( 72 , 121 , 145 ), 1f, 0 .5f, 1f, 1 .5f); document.add(cat); document.add( new Chunk( " " )); } // -- newline document.add(Chunk.NEWLINE); // 4.Tags Chunk tg = null ; document.add( new Chunk( "Tags: " )); for (String tag:article.getTags()){ tg = new Chunk(tag, fontTag); tg.setBackground( new BaseColor( 97 , 97 , 97 ), 1f, 0 .5f, 1f, 1 .5f); document.add(tg); document.add( new Chunk( " " )); } // -- newline document.add(Chunk.NEWLINE); document.add(Chunk.NEWLINE); } } } |
- /src/main/java/com/hmkcode/view/ExcelView.java
This class create Excel document using Apache POI-XSSF
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | package com.hmkcode.view; import java.util.LinkedList; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.poi.ss.formula.functions.Hyperlink; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import com.hmkcode.view.abstractview.AbstractExcelView; import com.hmkcode.vo.Article; public class ExcelView extends AbstractExcelView { @Override protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { Sheet sheet = workbook.createSheet( "sheet 1" ); @SuppressWarnings ( "unchecked" ) LinkedList<Article> articles = (LinkedList<Article>) model.get( "articles" ); Row row = null ; Cell cell = null ; int r = 0 ; int c = 0 ; //Style for header cell CellStyle style = workbook.createCellStyle(); style.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.index); style.setFillPattern(CellStyle.SOLID_FOREGROUND); style.setAlignment(CellStyle.ALIGN_CENTER); //Create header cells row = sheet.createRow(r++); cell = row.createCell(c++); cell.setCellStyle(style); cell.setCellValue( "Title" ); cell = row.createCell(c++); cell.setCellStyle(style); cell.setCellValue( "URL" ); cell = row.createCell(c++); cell.setCellStyle(style); cell.setCellValue( "Categories" ); cell = row.createCell(c++); cell.setCellStyle(style); cell.setCellValue( "Tags" ); //Create data cell for (Article article:articles){ row = sheet.createRow(r++); c = 0 ; row.createCell(c++).setCellValue(article.getTitle()); row.createCell(c++).setCellValue(article.getUrl()); row.createCell(c++).setCellValue(article.getCategories().toString()); row.createCell(c++).setCellValue(article.getTags().toString()); } for ( int i = 0 ; i < 4 ; i++) sheet.autoSizeColumn(i, true ); } } |