View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kns.web.struts.action;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.struts.action.ActionForm;
21  import org.apache.struts.action.ActionForward;
22  import org.apache.struts.action.ActionMapping;
23  import org.apache.struts.action.RedirectingActionForward;
24  import org.kuali.rice.core.api.util.RiceConstants;
25  import org.kuali.rice.core.api.util.RiceKeyConstants;
26  import org.kuali.rice.kim.api.KimConstants;
27  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
28  import org.kuali.rice.kns.inquiry.Inquirable;
29  import org.kuali.rice.kns.util.FieldUtils;
30  import org.kuali.rice.kns.util.WebUtils;
31  import org.kuali.rice.kns.web.struts.form.InquiryForm;
32  import org.kuali.rice.kns.web.ui.Field;
33  import org.kuali.rice.kns.web.ui.Row;
34  import org.kuali.rice.kns.web.ui.Section;
35  import org.kuali.rice.krad.bo.Attachment;
36  import org.kuali.rice.krad.bo.BusinessObject;
37  import org.kuali.rice.krad.bo.Exporter;
38  import org.kuali.rice.krad.bo.Note;
39  import org.kuali.rice.krad.bo.PersistableAttachment;
40  import org.kuali.rice.krad.bo.PersistableAttachmentList;
41  import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
42  import org.kuali.rice.krad.exception.AuthorizationException;
43  import org.kuali.rice.krad.service.KRADServiceLocator;
44  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
45  import org.kuali.rice.krad.service.ModuleService;
46  import org.kuali.rice.krad.service.NoteService;
47  import org.kuali.rice.krad.util.GlobalVariables;
48  import org.kuali.rice.krad.util.KRADConstants;
49  import org.kuali.rice.krad.util.KRADUtils;
50  
51  import javax.servlet.http.HttpServletRequest;
52  import javax.servlet.http.HttpServletResponse;
53  import java.io.ByteArrayOutputStream;
54  import java.io.IOException;
55  import java.lang.reflect.Method;
56  import java.util.Collections;
57  import java.util.List;
58  import java.util.Map;
59  
60  /**
61   * This class handles actions for inquiries of business objects.
62   */
63  public class KualiInquiryAction extends KualiAction {
64      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiInquiryAction.class);
65      private NoteService noteService;
66  
67      @Override
68      protected void checkAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
69          if (!(form instanceof InquiryForm)) {
70              super.checkAuthorization(form, methodToCall);
71          } else {
72              try {
73              	if(!KRADConstants.DOWNLOAD_BO_ATTACHMENT_METHOD.equals(methodToCall)){
74              		Class businessObjectClass = Class.forName(((InquiryForm) form).getBusinessObjectClassName());
75              		if (!KimApiServiceLocator.getPermissionService().isAuthorizedByTemplate(
76                              GlobalVariables.getUserSession().getPrincipalId(), KRADConstants.KNS_NAMESPACE,
77                              KimConstants.PermissionTemplateNames.INQUIRE_INTO_RECORDS,
78                              KRADUtils.getNamespaceAndComponentSimpleName(businessObjectClass),
79                              Collections.<String, String>emptyMap())) {
80  
81              			throw new AuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(), 
82                      		"inquire",
83                      		businessObjectClass.getSimpleName());
84              		}
85              	}
86              }
87              catch (ClassNotFoundException e) {
88              	LOG.warn("Unable to load BusinessObject class: " + ((InquiryForm) form).getBusinessObjectClassName(), e);
89                  super.checkAuthorization(form, methodToCall);
90              }
91          }
92      }
93  
94      @Override
95  	protected Map<String, String> getRoleQualification(ActionForm form,
96  			String methodToCall) {
97  		Map<String, String> roleQualification = super.getRoleQualification(
98  				form, methodToCall);
99  		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 }