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}