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}