001/**
002 * Copyright 2005-2016 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.kns.web.struts.action;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.struts.action.ActionForm;
021import org.apache.struts.action.ActionForward;
022import org.apache.struts.action.ActionMapping;
023import org.apache.struts.action.RedirectingActionForward;
024import org.kuali.rice.core.api.util.RiceConstants;
025import org.kuali.rice.core.api.util.RiceKeyConstants;
026import org.kuali.rice.kim.api.KimConstants;
027import org.kuali.rice.kim.api.services.KimApiServiceLocator;
028import org.kuali.rice.kns.inquiry.Inquirable;
029import org.kuali.rice.kns.util.FieldUtils;
030import org.kuali.rice.kns.util.WebUtils;
031import org.kuali.rice.kns.web.struts.form.InquiryForm;
032import org.kuali.rice.kns.web.ui.Field;
033import org.kuali.rice.kns.web.ui.Row;
034import org.kuali.rice.kns.web.ui.Section;
035import org.kuali.rice.krad.bo.Attachment;
036import org.kuali.rice.krad.bo.BusinessObject;
037import org.kuali.rice.krad.bo.Exporter;
038import org.kuali.rice.krad.bo.Note;
039import org.kuali.rice.krad.bo.PersistableAttachment;
040import org.kuali.rice.krad.bo.PersistableAttachmentList;
041import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
042import org.kuali.rice.krad.exception.AuthorizationException;
043import org.kuali.rice.krad.service.KRADServiceLocator;
044import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
045import org.kuali.rice.krad.service.ModuleService;
046import org.kuali.rice.krad.service.NoteService;
047import org.kuali.rice.krad.util.GlobalVariables;
048import org.kuali.rice.krad.util.KRADConstants;
049import org.kuali.rice.krad.util.KRADUtils;
050
051import javax.servlet.http.HttpServletRequest;
052import javax.servlet.http.HttpServletResponse;
053import java.io.ByteArrayOutputStream;
054import java.io.IOException;
055import java.lang.reflect.Method;
056import java.util.Collections;
057import java.util.List;
058import java.util.Map;
059
060/**
061 * This class handles actions for inquiries of business objects.
062 */
063public class KualiInquiryAction extends KualiAction {
064    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiInquiryAction.class);
065    private NoteService noteService;
066
067    @Override
068    protected void checkAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
069        if (!(form instanceof InquiryForm)) {
070            super.checkAuthorization(form, methodToCall);
071        } else {
072            try {
073                if(!KRADConstants.DOWNLOAD_BO_ATTACHMENT_METHOD.equals(methodToCall)){
074                        Class businessObjectClass = Class.forName(((InquiryForm) form).getBusinessObjectClassName());
075                        if (!KimApiServiceLocator.getPermissionService().isAuthorizedByTemplate(
076                            GlobalVariables.getUserSession().getPrincipalId(), KRADConstants.KNS_NAMESPACE,
077                            KimConstants.PermissionTemplateNames.INQUIRE_INTO_RECORDS,
078                            KRADUtils.getNamespaceAndComponentSimpleName(businessObjectClass),
079                            Collections.<String, String>emptyMap())) {
080
081                                throw new AuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(), 
082                                "inquire",
083                                businessObjectClass.getSimpleName());
084                        }
085                }
086            }
087            catch (ClassNotFoundException e) {
088                LOG.warn("Unable to load BusinessObject class: " + ((InquiryForm) form).getBusinessObjectClassName(), e);
089                super.checkAuthorization(form, methodToCall);
090            }
091        }
092    }
093
094    @Override
095        protected Map<String, String> getRoleQualification(ActionForm form,
096                        String methodToCall) {
097                Map<String, String> roleQualification = super.getRoleQualification(
098                                form, methodToCall);
099                if (form instanceof InquiryForm) {
100                        Map<String, String> primaryKeys = ((InquiryForm) form)
101                                        .getInquiryPrimaryKeys();
102                        if (primaryKeys != null) {
103                                for (String keyName : primaryKeys.keySet()) {
104                                        roleQualification.put(keyName, primaryKeys.get(keyName));                                       
105                                }
106                        }
107                }
108                return roleQualification;
109        }
110
111        @Override
112    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
113        request.setAttribute(KRADConstants.PARAM_MAINTENANCE_VIEW_MODE, KRADConstants.PARAM_MAINTENANCE_VIEW_MODE_INQUIRY);
114        return super.execute(mapping, form, request, response);
115    }
116
117    /**
118     * Gets an inquirable impl from the impl service name parameter. Then calls lookup service to retrieve the record from the
119     * key/value parameters. Finally gets a list of Rows from the inquirable
120     */
121    public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
122        InquiryForm inquiryForm = (InquiryForm) form;
123        if (inquiryForm.getBusinessObjectClassName() == null) {
124            LOG.error("Business object name not given.");
125            throw new RuntimeException("Business object name not given.");
126        }
127        
128        Class boClass = Class.forName(inquiryForm.getBusinessObjectClassName());
129        ModuleService responsibleModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(boClass);
130                if(responsibleModuleService!=null && responsibleModuleService.isExternalizable(boClass)){
131                        String redirectUrl = responsibleModuleService.getExternalizableBusinessObjectInquiryUrl(boClass, (Map<String, String[]>) request.getParameterMap());
132                        ActionForward redirectingActionForward = new RedirectingActionForward(redirectUrl);
133                        redirectingActionForward.setModule("/");
134                        return redirectingActionForward;
135                }
136
137                return continueWithInquiry(mapping, form, request, response);
138    }
139
140
141    /**
142     * Downloads the attachment to the user's browser
143     *
144     * @param mapping
145     * @param form
146     * @param request
147     * @param response
148     * @return ActionForward
149     * @throws Exception
150     */
151    public ActionForward downloadAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
152        InquiryForm inquiryForm = (InquiryForm) form;
153        int line = getSelectedLine(request);
154
155        BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
156        if (line < 0) {
157            if (bo instanceof PersistableAttachment) {
158                PersistableAttachment attachment = (PersistableAttachment)bo;
159                if (StringUtils.isNotBlank(attachment.getFileName())
160                        && attachment.getAttachmentContent() != null) {
161                    streamToResponse(attachment.getAttachmentContent(), attachment.getFileName(), attachment.getContentType(), response);
162                }
163            }
164        } else {
165            if (bo instanceof PersistableAttachmentList) {
166                PersistableAttachmentList<PersistableAttachment> attachmentsBo = (PersistableAttachmentList<PersistableAttachment>)bo;
167                if (CollectionUtils.isEmpty(attachmentsBo.getAttachments())) {
168                    return null;
169                }
170
171                List<? extends PersistableAttachment> attachments = attachmentsBo.getAttachments();
172                if (CollectionUtils.isNotEmpty(attachments)
173                        && attachments.size() > line) {
174                    PersistableAttachment attachment = (PersistableAttachment)attachmentsBo.getAttachments().get(line);
175                    streamToResponse(attachment.getAttachmentContent(), attachment.getFileName(), attachment.getContentType(), response);
176                }
177            }
178        }
179        return null;
180    }
181
182    public ActionForward downloadCustomBOAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
183        String fileName = request.getParameter(KRADConstants.BO_ATTACHMENT_FILE_NAME);
184                String contentType = request.getParameter(KRADConstants.BO_ATTACHMENT_FILE_CONTENT_TYPE);
185                String fileContentBoField = request.getParameter(KRADConstants.BO_ATTACHMENT_FILE_CONTENT_FIELD);
186        //require certain request properties
187        if (fileName != null
188                        && contentType != null
189                        && fileContentBoField != null) {
190                //make sure user has authorization to download attachment
191                checkAuthorization(form, findMethodToCall(form, request));
192                
193                fileName = StringUtils.replace(fileName, " ", "_");
194                
195                InquiryForm inquiryForm = (InquiryForm) form;
196                BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
197                checkBO(bo);
198                
199                Class clazz = (bo.getClass());
200                Method method = clazz.getMethod("get"+StringUtils.capitalize(fileContentBoField));
201                byte[] fileContents = (byte[]) method.invoke(bo);
202                streamToResponse(fileContents, fileName, contentType,response);
203        } else {
204                if (fileName == null) {
205                        LOG.error("Request Parameter \""+ KRADConstants.BO_ATTACHMENT_FILE_NAME + "\" not provided.");
206                }
207                if (contentType == null) {
208                        LOG.error("Request Parameter \""+ KRADConstants.BO_ATTACHMENT_FILE_CONTENT_TYPE + "\" not provided.");
209                }
210                if (fileContentBoField == null) {
211                        LOG.error("Request Parameter \""+ KRADConstants.BO_ATTACHMENT_FILE_CONTENT_FIELD + "\" not provided.");
212                }
213        }
214        return null;
215    }
216    
217    
218    /**
219     * Downloads the selected attachment to the user's browser
220     *
221     * @param mapping
222     * @param form
223     * @param request
224     * @param response
225     * @return ActionForward
226     * @throws Exception
227     */
228    public ActionForward downloadBOAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
229        Long noteIdentifier = Long.valueOf(request.getParameter(KRADConstants.NOTE_IDENTIFIER));
230
231        Note note = this.getNoteService().getNoteByNoteId(noteIdentifier);
232        if(note != null){
233                Attachment attachment = note.getAttachment();
234                if(attachment != null){
235                        //make sure attachment is setup with backwards reference to note (rather then doing this we could also just call the attachment service (with a new method that took in the note)
236                        attachment.setNote(note);
237                        WebUtils.saveMimeInputStreamAsFile(response, attachment.getAttachmentMimeTypeCode(), attachment.getAttachmentContents(), attachment.getAttachmentFileName(), attachment.getAttachmentFileSize().intValue());
238                }
239                return null;
240        }
241        
242        return mapping.findForward(RiceConstants.MAPPING_BASIC);
243    }
244    
245    public ActionForward continueWithInquiry(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
246        InquiryForm inquiryForm = (InquiryForm) form;
247        
248        if (inquiryForm.getBusinessObjectClassName() == null) {
249                LOG.error("Business object name not given.");
250                throw new RuntimeException("Business object name not given.");
251        }
252        
253        BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
254        checkBO(bo);
255        
256        populateSections(mapping, request, inquiryForm, bo);
257        
258        return mapping.findForward(RiceConstants.MAPPING_BASIC);
259    }
260    
261    /**
262     * Turns on (or off) the inactive record display for a maintenance collection.
263     */
264    public ActionForward toggleInactiveRecordDisplay(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
265        InquiryForm inquiryForm = (InquiryForm) form;
266        if (inquiryForm.getBusinessObjectClassName() == null) {
267            LOG.error("Business object name not given.");
268            throw new RuntimeException("Business object name not given.");
269        }
270        
271        BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
272        checkBO(bo);
273        
274        Inquirable kualiInquirable = inquiryForm.getInquirable();
275        //////////////////////////////
276        String collectionName = extractCollectionName(request, KRADConstants.TOGGLE_INACTIVE_METHOD);
277        if (collectionName == null) {
278            LOG.error("Unable to get find collection name in request.");
279            throw new RuntimeException("Unable to get find collection class in request.");
280        }  
281        String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
282        boolean showInactive = Boolean.parseBoolean(StringUtils.substringBetween(parameterName, KRADConstants.METHOD_TO_CALL_BOPARM_LEFT_DEL, "."));
283        kualiInquirable.setShowInactiveRecords(collectionName, showInactive);
284        //////////////////////////////
285        
286        populateSections(mapping, request, inquiryForm, bo);
287
288        // toggling the display to be visible again, re-open any previously closed inactive records
289        if (showInactive) {
290                WebUtils.reopenInactiveRecords(inquiryForm.getSections(), inquiryForm.getTabStates(), collectionName);
291        }
292        
293        return mapping.findForward(RiceConstants.MAPPING_BASIC);
294    }
295
296    @Override
297    public ActionForward toggleTab(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
298        InquiryForm inquiryForm = (InquiryForm) form;
299        if (inquiryForm.getBusinessObjectClassName() == null) {
300            LOG.error("Business object name not given.");
301            throw new RuntimeException("Business object name not given.");
302        }
303        
304        BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
305        checkBO(bo);
306        
307        populateSections(mapping, request, inquiryForm, bo);
308        
309        Inquirable kualiInquirable = inquiryForm.getInquirable();
310        
311        return super.toggleTab(mapping, form, request, response);
312    }
313    
314    
315    
316    /**
317         * @see org.kuali.rice.krad.web.struts.action.KualiAction#hideAllTabs(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
318         */
319        @Override
320        public ActionForward hideAllTabs(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
321        // KULRICE-2852: Overrides hideAllTabs() so that it also calls the populateSections() method.
322                InquiryForm inquiryForm = (InquiryForm) form;
323        if (inquiryForm.getBusinessObjectClassName() == null) {
324            LOG.error("Business object name not given.");
325            throw new RuntimeException("Business object name not given.");
326        }
327        
328        BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
329        checkBO(bo);
330        
331        populateSections(mapping, request, inquiryForm, bo);
332                
333                return super.hideAllTabs(mapping, form, request, response);
334        }
335
336        /**
337         * @see org.kuali.rice.krad.web.struts.action.KualiAction#showAllTabs(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
338         */
339        @Override
340        public ActionForward showAllTabs(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
341        // KULRICE-2852: Overrides showAllTabs() so that it also calls the populateSections() method.
342                InquiryForm inquiryForm = (InquiryForm) form;
343        if (inquiryForm.getBusinessObjectClassName() == null) {
344            LOG.error("Business object name not given.");
345            throw new RuntimeException("Business object name not given.");
346        }
347        
348        BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
349        checkBO(bo);
350        
351        populateSections(mapping, request, inquiryForm, bo);
352                
353                return super.showAllTabs(mapping, form, request, response);
354        }
355
356        /**
357     * Handles exporting the BusinessObject for this Inquiry to XML if it has a custom XML exporter available.
358     */
359    public ActionForward export(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
360        InquiryForm inquiryForm = (InquiryForm) form;
361        if (inquiryForm.isCanExport()) {
362                BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
363                checkBO(bo);
364                if (bo != null) {
365                        BusinessObjectEntry businessObjectEntry = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(inquiryForm.getBusinessObjectClassName());
366                        Class<? extends Exporter> exporterClass = businessObjectEntry.getExporterClass();
367                        if (exporterClass != null) {
368                                Exporter exporter = exporterClass.newInstance();
369                                response.setContentType(KRADConstants.XML_MIME_TYPE);
370                                response.setHeader("Content-disposition", "attachment; filename=export.xml");
371                                exporter.export(businessObjectEntry.getBusinessObjectClass(), Collections.singletonList(bo), KRADConstants.XML_FORMAT, response.getOutputStream());
372                        }
373                } else {
374                        //show the empty section with error
375                        populateSections(mapping, request, inquiryForm, bo);
376                        return mapping.findForward(RiceConstants.MAPPING_BASIC); 
377                }
378        }
379        
380        return null;
381    }
382
383    /**
384     * Convert a Request into a Map<String,String>. Technically, Request parameters do not neatly translate into a Map of Strings,
385     * because a given parameter may legally appear more than once (so a Map of String[] would be more accurate.) This method should
386     * be safe for business objects, but may not be reliable for more general uses.
387     */
388    protected String extractCollectionName(HttpServletRequest request, String methodToCall) {
389        // collection name and underlying object type from request parameter
390        String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
391        String collectionName = null;
392        if (StringUtils.isNotBlank(parameterName)) {
393            collectionName = StringUtils.substringBetween(parameterName, methodToCall + ".", ".(");
394        }
395        return collectionName;
396    }
397    
398    protected BusinessObject retrieveBOFromInquirable(InquiryForm inquiryForm) {
399        Inquirable kualiInquirable = inquiryForm.getInquirable();
400        // retrieve the business object
401        BusinessObject bo = kualiInquirable.getBusinessObject(inquiryForm.retrieveInquiryDecryptedPrimaryKeys());
402        if (bo == null) {
403            LOG.error("No records found in inquiry action.");
404            GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS, RiceKeyConstants.ERROR_INQUIRY);
405        }
406        return bo;
407    }
408    
409    protected void populateSections(ActionMapping mapping, HttpServletRequest request, InquiryForm inquiryForm, BusinessObject bo) {
410        Inquirable kualiInquirable = inquiryForm.getInquirable();
411        
412        if (bo != null) {
413                // get list of populated sections for display
414                List<Section> sections = kualiInquirable.getSections(bo);
415                inquiryForm.setSections(sections);
416                kualiInquirable.addAdditionalSections(sections, bo);
417        } else {
418                inquiryForm.setSections(getEmptySections(kualiInquirable.getTitle()));
419        }
420
421        request.setAttribute(KRADConstants.INQUIRABLE_ATTRIBUTE_NAME, kualiInquirable);
422    }
423    
424    /**
425    *
426    * Handy method to stream the byte array to response object
427    * @param attachmentDataSource
428    * @param response
429    * @throws Exception
430    */
431   protected void streamToResponse(byte[] fileContents, String fileName, String fileContentType,HttpServletResponse response) throws Exception{
432       ByteArrayOutputStream baos = null;
433       try{
434           baos = new ByteArrayOutputStream(fileContents.length);
435           baos.write(fileContents);
436           WebUtils.saveMimeOutputStreamAsFile(response, fileContentType, baos, fileName);
437       }finally{
438           try{
439               if(baos!=null){
440                   baos.close();
441                   baos = null;
442               }
443           }catch(IOException ioEx){
444               LOG.error("Error while downloading attachment");
445               throw new RuntimeException("IOException occurred while downloading attachment", ioEx);
446           }
447       }
448   }
449    /**
450     * Returns a section list with one empty section and one row.
451     * 
452     * @return list of sections
453     */
454    private List<Section> getEmptySections(String title) {
455        final Row row = new Row(Collections.<Field>emptyList());
456        
457        final Section section = new Section(Collections.singletonList(row));
458                section.setErrorKey("*");
459                section.setSectionTitle(title != null ? title : "");
460                section.setNumberOfColumns(0);
461                
462                return Collections.singletonList(section);
463    }
464    
465    /**
466     * throws an exception if BO fails the check.
467     * @param bo the BusinessObject to check.
468     * @throws UnsupportedOperationException if BO is null & no messages have been generated.
469     */
470    private void checkBO(BusinessObject bo) {
471        if (bo == null && GlobalVariables.getMessageMap().hasNoMessages()) {
472                throw new UnsupportedOperationException("The record you have inquired on does not exist.");
473        }
474    }
475    
476    protected NoteService getNoteService() {
477                if ( noteService == null ) {
478                        noteService = KRADServiceLocator.getNoteService();
479                }
480                return this.noteService;
481        }
482}