001/** 002 * Copyright 2005-2018 The Kuali Foundation 003 * 004 * Licensed under the Educational Community 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 * http://www.opensource.org/licenses/ecl2.php 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 */ 016package org.kuali.rice.krad.web.controller; 017 018import org.apache.commons.collections.CollectionUtils; 019import org.apache.commons.lang.StringUtils; 020import org.kuali.rice.krad.bo.Exporter; 021import org.kuali.rice.krad.datadictionary.DataDictionary; 022import org.kuali.rice.krad.datadictionary.DataObjectEntry; 023import org.kuali.rice.krad.service.DataDictionaryService; 024import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 025import org.kuali.rice.krad.uif.UifConstants; 026import org.kuali.rice.krad.uif.UifParameters; 027import org.kuali.rice.krad.uif.container.CollectionGroup; 028import org.kuali.rice.krad.uif.layout.collections.TableExporter; 029import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle; 030import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 031import org.kuali.rice.krad.util.GlobalVariables; 032import org.kuali.rice.krad.util.KRADConstants; 033import org.kuali.rice.krad.web.form.InquiryForm; 034import org.kuali.rice.krad.web.form.UifFormBase; 035import org.springframework.beans.factory.annotation.Autowired; 036import org.springframework.stereotype.Controller; 037import org.springframework.validation.BindingResult; 038import org.springframework.web.bind.annotation.ModelAttribute; 039import org.springframework.web.bind.annotation.RequestMapping; 040import org.springframework.web.bind.annotation.RequestMethod; 041import org.springframework.web.bind.annotation.ResponseBody; 042import org.springframework.web.servlet.ModelAndView; 043 044import javax.servlet.http.HttpServletRequest; 045import javax.servlet.http.HttpServletResponse; 046import java.util.Collections; 047import java.util.List; 048 049/** 050 * Controller that handles table export requests. 051 * 052 * @author Kuali Rice Team (rice.collab@kuali.org) 053 */ 054@Controller 055@RequestMapping(value = "/export") 056public class UifExportController extends UifControllerBase { 057 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(UifExportController.class); 058 059 @Autowired 060 protected HttpServletRequest request; 061 062 /** 063 * Retrieves the session form for the form key request parameter so we can initialize a form instance of the 064 * same type the view was rendered with. 065 * 066 * {@inheritDoc} 067 */ 068 @Override 069 protected UifFormBase createInitialForm() { 070 String formKey = request.getParameter(UifParameters.FORM_KEY); 071 if (StringUtils.isBlank(formKey)) { 072 throw new RuntimeException("Unable to create export form due to misssing form key parameter"); 073 } 074 075 UifFormBase sessionForm = GlobalVariables.getUifFormManager().getSessionForm(formKey); 076 if (sessionForm != null) { 077 try { 078 return sessionForm.getClass().newInstance(); 079 } catch (Exception e) { 080 throw new RuntimeException("Cannot create export form instance from session form", e); 081 } 082 } 083 084 return null; 085 } 086 087 /** 088 * Generates exportable table data as CSV based on the rich table selected. 089 */ 090 @MethodAccessible 091 @RequestMapping(method = RequestMethod.GET, params = "methodToCall=" + UifConstants.MethodToCallNames.TABLE_CSV, 092 produces = {"text/csv"}) 093 @ResponseBody 094 public String tableCsvRetrieval(@ModelAttribute("KualiForm") UifFormBase form, HttpServletRequest request, 095 HttpServletResponse response) { 096 LOG.debug("processing csv table data request"); 097 098 return retrieveTableData(form, request, response); 099 } 100 101 /** 102 * Generates exportable table data in xsl based on the rich table selected. 103 */ 104 @MethodAccessible 105 @RequestMapping(method = RequestMethod.GET, params = "methodToCall=" + UifConstants.MethodToCallNames.TABLE_XLS, 106 produces = {"application/vnd.ms-excel"}) 107 @ResponseBody 108 public String tableXlsRetrieval(@ModelAttribute("KualiForm") UifFormBase form, HttpServletRequest request, 109 HttpServletResponse response) { 110 LOG.debug("processing xls table data request"); 111 112 return retrieveTableData(form, request, response); 113 } 114 115 /** 116 * Generates exportable table data based on the rich table selected. 117 */ 118 @MethodAccessible 119 @RequestMapping(method = RequestMethod.GET, params = "methodToCall=" + UifConstants.MethodToCallNames.TABLE_XML, 120 produces = {"application/xml"}) 121 @ResponseBody 122 public String tableXmlRetrieval(@ModelAttribute("KualiForm") UifFormBase form, HttpServletRequest request, 123 HttpServletResponse response) { 124 LOG.debug("processing xml table data request"); 125 126 return retrieveTableData(form, request, response); 127 } 128 129 /** 130 * Handles exporting the dataObject for this Inquiry to XML if it has a custom XML exporter available. 131 * 132 * @param form KualiForm 133 * @param result interface that represents binding results 134 * @param request the http request that was made 135 * @param response the http response object 136 */ 137 @RequestMapping(method = RequestMethod.GET, params = "methodToCall=" + UifConstants.MethodToCallNames.INQUIRY_XML, 138 produces = {"application/xml"}) 139 @ResponseBody 140 public ModelAndView inquiryXmlRetrieval(@ModelAttribute("KualiForm") UifFormBase form, BindingResult result, 141 HttpServletRequest request, HttpServletResponse response) throws Exception { 142 InquiryForm inquiryForm = (InquiryForm) form; 143 Object dataObject = inquiryForm.getDataObject(); 144 145 if (dataObject != null) { 146 applyCustomExport(Collections.singletonList(dataObject), inquiryForm.getDataObjectClassName(), 147 KRADConstants.XML_FORMAT, response); 148 } 149 150 return null; 151 } 152 153 154 /** 155 * Generates exportable table data based on the rich table selected. 156 * 157 * <p>First the lifecycle process is run to rebuild the collection group, then 158 * {@link org.kuali.rice.krad.uif.layout.collections.TableExporter} is invoked to build the export data from 159 * the collection.</p> 160 */ 161 protected String retrieveTableData(@ModelAttribute("KualiForm") UifFormBase form, HttpServletRequest request, 162 HttpServletResponse response) { 163 LOG.debug("processing table data request"); 164 165 CollectionGroup collectionGroup = (CollectionGroup) ViewLifecycle.performComponentLifecycle(form.getView(), 166 form, request, form.getViewPostMetadata(), form.getUpdateComponentId()); 167 168 List<Object> modelCollection = ObjectPropertyUtils.getPropertyValue(form, 169 collectionGroup.getBindingInfo().getBindingPath()); 170 171 172 Class<?> dataObjectClass = collectionGroup.getCollectionObjectClass(); 173 String formatType = getValidatedFormatType(request.getParameter(UifParameters.FORMAT_TYPE)); 174 175 // set update none to prevent the lifecycle from being run after the controller finishes 176 form.setAjaxReturnType(UifConstants.AjaxReturnTypes.UPDATENONE.getKey()); 177 178 if (applyCustomExport(modelCollection, dataObjectClass.getName(), formatType, response)) { 179 return null; 180 } 181 182 // generic export 183 return TableExporter.buildExportTableData(collectionGroup, form, formatType); 184 } 185 186 /** 187 * Checks if a custom exporter can be applied. 188 * 189 * @param dataObjectEntry the data dictionary entry for the data object 190 * 191 * @return true if a custom exporter can be found, false otherwise 192 */ 193 protected boolean canApplyCustomExport(DataObjectEntry dataObjectEntry) { 194 if (dataObjectEntry == null) { 195 return false; 196 } 197 198 return dataObjectEntry.getExporterClass() != null; 199 } 200 201 /** 202 * Applies custom export if an exporter class is defined. Will return false if no exporter class defined 203 * or if the dataObject collection is empty. 204 * 205 * 206 * @param dataObjectCollection 207 * @param dataObjectClassName 208 * @param formatType 209 * 210 * @param response true if custom exporter applied else return false. 211 * 212 */ 213 protected boolean applyCustomExport(List<Object> dataObjectCollection, String dataObjectClassName, 214 String formatType, HttpServletResponse response) { 215 216 String contentType = getContentType(formatType); 217 setAttachmentResponseHeader(response, "export." + formatType, contentType); 218 219 220 // check for custom exporter class defined for the data object class 221 DataObjectEntry dataObjectEntry = 222 KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDataObjectEntry( 223 dataObjectClassName); 224 225 // Return if no dataobject present to export 226 if (CollectionUtils.isEmpty(dataObjectCollection)) { 227 return false; 228 } 229 230 // No custom exporter present 231 if (!canApplyCustomExport(dataObjectEntry)) { 232 return false; 233 } 234 235 try { 236 Exporter exporter = dataObjectEntry.getExporterClass().newInstance(); 237 238 if (exporter.getSupportedFormats(dataObjectEntry.getDataObjectClass()).contains(formatType)) { 239 exporter.export(dataObjectEntry.getDataObjectClass(), dataObjectCollection, formatType, response.getOutputStream()); 240 } 241 } catch (Exception e) { 242 throw new RuntimeException("Cannot invoked custom exporter class", e); 243 } 244 245 return false; 246 } 247 248 /** 249 * Creates consistent setup of attachment response header. 250 * 251 * @param response http response object 252 * @param filename name of the return file 253 * @param contentType return content type 254 */ 255 protected void setAttachmentResponseHeader(HttpServletResponse response, String filename, String contentType) { 256 response.setContentType(contentType); 257 response.setHeader("Content-disposition", "attachment; filename=\"" + filename + "\""); 258 response.setHeader("Expires", "0"); 259 response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); 260 response.setHeader("Pragma", "public"); 261 } 262 263 /** 264 * Reviews and returns a valid format type, defaults to csv. 265 * 266 * @param formatType format type to validate 267 * @return valid format type 268 */ 269 protected String getValidatedFormatType(String formatType) { 270 if (KRADConstants.EXCEL_FORMAT.equals(formatType) || KRADConstants.XML_FORMAT.equals(formatType)) { 271 return formatType; 272 } 273 274 return KRADConstants.CSV_FORMAT; 275 } 276 277 /** 278 * Reviews and returns a valid content type, defaults to text/csv. 279 * 280 * @param formatType format type to return content type for 281 * @return valid content type 282 */ 283 protected String getContentType(String formatType) { 284 if (KRADConstants.EXCEL_FORMAT.equals(formatType)) { 285 return KRADConstants.EXCEL_MIME_TYPE; 286 } else if (KRADConstants.XML_FORMAT.equals(formatType)) { 287 return KRADConstants.XML_MIME_TYPE; 288 } else { 289 return KRADConstants.CSV_MIME_TYPE; 290 } 291 } 292}