001/* 002 * Copyright 2002-2015 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.view.jasperreports; 018 019import java.util.HashMap; 020import java.util.Map; 021import java.util.Properties; 022import javax.servlet.http.HttpServletResponse; 023 024import net.sf.jasperreports.engine.JasperPrint; 025 026import org.springframework.beans.BeanUtils; 027import org.springframework.util.CollectionUtils; 028 029/** 030 * JasperReports view class that allows for the actual rendering format 031 * to be specified at runtime using a parameter contained in the model. 032 * 033 * <p>This view works on the concept of a format key and a mapping key. 034 * The format key is used to pass the mapping key from your {@code Controller} 035 * to Spring through as part of the model and the mapping key is used to map 036 * a logical format to an actual JasperReports view class. 037 * 038 * <p>For example, you might add the following code to your {@code Controller}: 039 * 040 * <pre class="code"> 041 * Map<String, Object> model = new HashMap<String, Object>(); 042 * model.put("format", "pdf");</pre> 043 * 044 * Here {@code format} is the format key and {@code pdf} is the mapping key. 045 * When rendering a report, this class looks for a model parameter under the 046 * format key, which by default is {@code format}. It then uses the value of 047 * this parameter to lookup the actual {@code View} class to use. 048 * 049 * <p>The default mappings for the format lookup are: 050 * 051 * <p><ul> 052 * <li>{@code csv} - {@code JasperReportsCsvView}</li> 053 * <li>{@code html} - {@code JasperReportsHtmlView}</li> 054 * <li>{@code pdf} - {@code JasperReportsPdfView}</li> 055 * <li>{@code xls} - {@code JasperReportsXlsView}</li> 056 * <li>{@code xlsx} - {@code JasperReportsXlsxView}</li> (as of Spring 4.2) 057 * </ul> 058 * 059 * <p>The format key can be changed using the {@code formatKey} property. 060 * The applicable key-to-view-class mappings can be configured using the 061 * {@code formatMappings} property. 062 * 063 * @author Rob Harrop 064 * @author Juergen Hoeller 065 * @since 1.1.5 066 * @see #setFormatKey 067 * @see #setFormatMappings 068 */ 069public class JasperReportsMultiFormatView extends AbstractJasperReportsView { 070 071 /** 072 * Default value used for format key: "format" 073 */ 074 public static final String DEFAULT_FORMAT_KEY = "format"; 075 076 077 /** 078 * The key of the model parameter that holds the format key. 079 */ 080 private String formatKey = DEFAULT_FORMAT_KEY; 081 082 /** 083 * Stores the format mappings, with the format discriminator 084 * as key and the corresponding view class as value. 085 */ 086 private Map<String, Class<? extends AbstractJasperReportsView>> formatMappings; 087 088 /** 089 * Stores the mappings of mapping keys to Content-Disposition header values. 090 */ 091 private Properties contentDispositionMappings; 092 093 094 /** 095 * Creates a new {@code JasperReportsMultiFormatView} instance 096 * with a default set of mappings. 097 */ 098 public JasperReportsMultiFormatView() { 099 this.formatMappings = new HashMap<String, Class<? extends AbstractJasperReportsView>>(4); 100 this.formatMappings.put("csv", JasperReportsCsvView.class); 101 this.formatMappings.put("html", JasperReportsHtmlView.class); 102 this.formatMappings.put("pdf", JasperReportsPdfView.class); 103 this.formatMappings.put("xls", JasperReportsXlsView.class); 104 this.formatMappings.put("xlsx", JasperReportsXlsxView.class); 105 } 106 107 108 /** 109 * Set the key of the model parameter that holds the format discriminator. 110 * Default is "format". 111 */ 112 public void setFormatKey(String formatKey) { 113 this.formatKey = formatKey; 114 } 115 116 /** 117 * Set the mappings of format discriminators to view class names. 118 * The default mappings are: 119 * <p><ul> 120 * <li>{@code csv} - {@code JasperReportsCsvView}</li> 121 * <li>{@code html} - {@code JasperReportsHtmlView}</li> 122 * <li>{@code pdf} - {@code JasperReportsPdfView}</li> 123 * <li>{@code xls} - {@code JasperReportsXlsView}</li> 124 * <li>{@code xlsx} - {@code JasperReportsXlsxView}</li> (as of Spring 4.2) 125 * </ul> 126 */ 127 public void setFormatMappings(Map<String, Class<? extends AbstractJasperReportsView>> formatMappings) { 128 if (CollectionUtils.isEmpty(formatMappings)) { 129 throw new IllegalArgumentException("'formatMappings' must not be empty"); 130 } 131 this.formatMappings = formatMappings; 132 } 133 134 /** 135 * Set the mappings of {@code Content-Disposition} header values to 136 * mapping keys. If specified, Spring will look at these mappings to determine 137 * the value of the {@code Content-Disposition} header for a given 138 * format mapping. 139 */ 140 public void setContentDispositionMappings(Properties mappings) { 141 this.contentDispositionMappings = mappings; 142 } 143 144 /** 145 * Return the mappings of {@code Content-Disposition} header values to 146 * mapping keys. Mainly available for configuration through property paths 147 * that specify individual keys. 148 */ 149 public Properties getContentDispositionMappings() { 150 if (this.contentDispositionMappings == null) { 151 this.contentDispositionMappings = new Properties(); 152 } 153 return this.contentDispositionMappings; 154 } 155 156 157 @Override 158 protected boolean generatesDownloadContent() { 159 return true; 160 } 161 162 /** 163 * Locates the format key in the model using the configured discriminator key and uses this 164 * key to lookup the appropriate view class from the mappings. The rendering of the 165 * report is then delegated to an instance of that view class. 166 */ 167 @Override 168 protected void renderReport(JasperPrint populatedReport, Map<String, Object> model, HttpServletResponse response) 169 throws Exception { 170 171 String format = (String) model.get(this.formatKey); 172 if (format == null) { 173 throw new IllegalArgumentException("No format found in model"); 174 } 175 176 if (logger.isDebugEnabled()) { 177 logger.debug("Rendering report using format mapping key [" + format + "]"); 178 } 179 180 Class<? extends AbstractJasperReportsView> viewClass = this.formatMappings.get(format); 181 if (viewClass == null) { 182 throw new IllegalArgumentException("Format discriminator [" + format + "] is not a configured mapping"); 183 } 184 185 if (logger.isDebugEnabled()) { 186 logger.debug("Rendering report using view class [" + viewClass.getName() + "]"); 187 } 188 189 AbstractJasperReportsView view = BeanUtils.instantiateClass(viewClass); 190 // Can skip most initialization since all relevant URL processing 191 // has been done - just need to convert parameters on the sub view. 192 view.setExporterParameters(getExporterParameters()); 193 view.setConvertedExporterParameters(getConvertedExporterParameters()); 194 195 // Prepare response and render report. 196 populateContentDispositionIfNecessary(response, format); 197 view.renderReport(populatedReport, model, response); 198 } 199 200 /** 201 * Adds/overwrites the {@code Content-Disposition} header value with the format-specific 202 * value if the mappings have been specified and a valid one exists for the given format. 203 * @param response the {@code HttpServletResponse} to set the header in 204 * @param format the format key of the mapping 205 * @see #setContentDispositionMappings 206 */ 207 private void populateContentDispositionIfNecessary(HttpServletResponse response, String format) { 208 if (this.contentDispositionMappings != null) { 209 String header = this.contentDispositionMappings.getProperty(format); 210 if (header != null) { 211 if (logger.isDebugEnabled()) { 212 logger.debug("Setting Content-Disposition header to: [" + header + "]"); 213 } 214 response.setHeader(HEADER_CONTENT_DISPOSITION, header); 215 } 216 } 217 } 218 219}