001    /**
002     * Copyright 2005-2012 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     */
016    package org.kuali.rice.kns.web.struts.action;
017    
018    import org.apache.commons.collections.CollectionUtils;
019    import org.apache.commons.lang.StringUtils;
020    import org.apache.struts.action.ActionForm;
021    import org.apache.struts.action.ActionForward;
022    import org.apache.struts.action.ActionMapping;
023    import org.apache.struts.action.RedirectingActionForward;
024    import org.kuali.rice.core.api.util.RiceConstants;
025    import org.kuali.rice.core.api.util.RiceKeyConstants;
026    import org.kuali.rice.kim.api.KimConstants;
027    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
028    import org.kuali.rice.kns.inquiry.Inquirable;
029    import org.kuali.rice.kns.util.FieldUtils;
030    import org.kuali.rice.kns.util.WebUtils;
031    import org.kuali.rice.kns.web.struts.form.InquiryForm;
032    import org.kuali.rice.kns.web.ui.Field;
033    import org.kuali.rice.kns.web.ui.Row;
034    import org.kuali.rice.kns.web.ui.Section;
035    import org.kuali.rice.krad.bo.Attachment;
036    import org.kuali.rice.krad.bo.BusinessObject;
037    import org.kuali.rice.krad.bo.Exporter;
038    import org.kuali.rice.krad.bo.Note;
039    import org.kuali.rice.krad.bo.PersistableAttachment;
040    import org.kuali.rice.krad.bo.PersistableAttachmentList;
041    import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
042    import org.kuali.rice.krad.exception.AuthorizationException;
043    import org.kuali.rice.krad.service.KRADServiceLocator;
044    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
045    import org.kuali.rice.krad.service.ModuleService;
046    import org.kuali.rice.krad.service.NoteService;
047    import org.kuali.rice.krad.util.GlobalVariables;
048    import org.kuali.rice.krad.util.KRADConstants;
049    import org.kuali.rice.krad.util.KRADUtils;
050    
051    import javax.servlet.http.HttpServletRequest;
052    import javax.servlet.http.HttpServletResponse;
053    import java.io.ByteArrayOutputStream;
054    import java.io.IOException;
055    import java.lang.reflect.Method;
056    import java.util.Collections;
057    import java.util.List;
058    import java.util.Map;
059    
060    /**
061     * This class handles actions for inquiries of business objects.
062     */
063    public 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    }