View Javadoc

1   /**
2    * Copyright 2005-2012 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.WebUtils;
30  import org.kuali.rice.kns.web.struts.form.InquiryForm;
31  import org.kuali.rice.kns.web.ui.Field;
32  import org.kuali.rice.kns.web.ui.Row;
33  import org.kuali.rice.kns.web.ui.Section;
34  import org.kuali.rice.krad.bo.Attachment;
35  import org.kuali.rice.krad.bo.BusinessObject;
36  import org.kuali.rice.krad.bo.Exporter;
37  import org.kuali.rice.krad.bo.Note;
38  import org.kuali.rice.krad.bo.PersistableAttachment;
39  import org.kuali.rice.krad.bo.PersistableAttachmentList;
40  import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
41  import org.kuali.rice.krad.exception.AuthorizationException;
42  import org.kuali.rice.krad.service.KRADServiceLocator;
43  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
44  import org.kuali.rice.krad.service.ModuleService;
45  import org.kuali.rice.krad.service.NoteService;
46  import org.kuali.rice.krad.util.GlobalVariables;
47  import org.kuali.rice.krad.util.KRADConstants;
48  import org.kuali.rice.krad.util.KRADUtils;
49  
50  import javax.servlet.http.HttpServletRequest;
51  import javax.servlet.http.HttpServletResponse;
52  import java.io.ByteArrayOutputStream;
53  import java.io.IOException;
54  import java.lang.reflect.Method;
55  import java.util.Collections;
56  import java.util.List;
57  import java.util.Map;
58  
59  /**
60   * This class handles actions for inquiries of business objects.
61   */
62  public class KualiInquiryAction extends KualiAction {
63      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiInquiryAction.class);
64      private NoteService noteService;
65  
66      @Override
67      protected void checkAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
68          if (!(form instanceof InquiryForm)) {
69              super.checkAuthorization(form, methodToCall);
70          } else {
71              try {
72              	if(!KRADConstants.DOWNLOAD_BO_ATTACHMENT_METHOD.equals(methodToCall)){
73              		Class businessObjectClass = Class.forName(((InquiryForm) form).getBusinessObjectClassName());
74              		if (!KimApiServiceLocator.getPermissionService().isAuthorizedByTemplate(
75                              GlobalVariables.getUserSession().getPrincipalId(), KRADConstants.KNS_NAMESPACE,
76                              KimConstants.PermissionTemplateNames.INQUIRE_INTO_RECORDS,
77                              KRADUtils.getNamespaceAndComponentSimpleName(businessObjectClass),
78                              Collections.<String, String>emptyMap())) {
79  
80              			throw new AuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(), 
81                      		"inquire",
82                      		businessObjectClass.getSimpleName());
83              		}
84              	}
85              }
86              catch (ClassNotFoundException e) {
87              	LOG.warn("Unable to load BusinessObject class: " + ((InquiryForm) form).getBusinessObjectClassName(), e);
88                  super.checkAuthorization(form, methodToCall);
89              }
90          }
91      }
92  
93      @Override
94  	protected Map<String, String> getRoleQualification(ActionForm form,
95  			String methodToCall) {
96  		Map<String, String> roleQualification = super.getRoleQualification(
97  				form, methodToCall);
98  		if (form instanceof InquiryForm) {
99  			Map<String, String> primaryKeys = ((InquiryForm) form)
100 					.getInquiryPrimaryKeys();
101 			if (primaryKeys != null) {
102 				for (String keyName : primaryKeys.keySet()) {
103 					roleQualification.put(keyName, primaryKeys.get(keyName));					
104 				}
105 			}
106 		}
107 		return roleQualification;
108 	}
109 
110 	@Override
111     public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
112         request.setAttribute(KRADConstants.PARAM_MAINTENANCE_VIEW_MODE, KRADConstants.PARAM_MAINTENANCE_VIEW_MODE_INQUIRY);
113         return super.execute(mapping, form, request, response);
114     }
115 
116     /**
117      * Gets an inquirable impl from the impl service name parameter. Then calls lookup service to retrieve the record from the
118      * key/value parameters. Finally gets a list of Rows from the inquirable
119      */
120     public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
121         InquiryForm inquiryForm = (InquiryForm) form;
122         if (inquiryForm.getBusinessObjectClassName() == null) {
123             LOG.error("Business object name not given.");
124             throw new RuntimeException("Business object name not given.");
125         }
126         
127         Class boClass = Class.forName(inquiryForm.getBusinessObjectClassName());
128         ModuleService responsibleModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(boClass);
129 		if(responsibleModuleService!=null && responsibleModuleService.isExternalizable(boClass)){
130 			String redirectUrl = responsibleModuleService.getExternalizableBusinessObjectInquiryUrl(boClass, (Map<String, String[]>) request.getParameterMap());
131 			ActionForward redirectingActionForward = new RedirectingActionForward(redirectUrl);
132 			redirectingActionForward.setModule("/");
133 			return redirectingActionForward;
134 		}
135 
136 		return continueWithInquiry(mapping, form, request, response);
137     }
138 
139 
140     /**
141      * Downloads the attachment to the user's browser
142      *
143      * @param mapping
144      * @param form
145      * @param request
146      * @param response
147      * @return ActionForward
148      * @throws Exception
149      */
150     public ActionForward downloadAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
151         InquiryForm inquiryForm = (InquiryForm) form;
152         int line = getSelectedLine(request);
153 
154         BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
155         if (line < 0) {
156             if (bo instanceof PersistableAttachment) {
157                 PersistableAttachment attachment = (PersistableAttachment)bo;
158                 if (StringUtils.isNotBlank(attachment.getFileName())
159                         && attachment.getAttachmentContent() != null) {
160                     streamToResponse(attachment.getAttachmentContent(), attachment.getFileName(), attachment.getContentType(), response);
161                 }
162             }
163         } else {
164             if (bo instanceof PersistableAttachmentList) {
165                 PersistableAttachmentList<PersistableAttachment> attachmentsBo = (PersistableAttachmentList<PersistableAttachment>)bo;
166                 if (CollectionUtils.isEmpty(attachmentsBo.getAttachments())) {
167                     return null;
168                 }
169 
170                 List<? extends PersistableAttachment> attachments = attachmentsBo.getAttachments();
171                 if (CollectionUtils.isNotEmpty(attachments)
172                         && attachments.size() > line) {
173                     PersistableAttachment attachment = (PersistableAttachment)attachmentsBo.getAttachments().get(line);
174                     streamToResponse(attachment.getAttachmentContent(), attachment.getFileName(), attachment.getContentType(), response);
175                 }
176             }
177         }
178         return null;
179     }
180 
181     public ActionForward downloadCustomBOAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
182     	String fileName = request.getParameter(KRADConstants.BO_ATTACHMENT_FILE_NAME);
183 		String contentType = request.getParameter(KRADConstants.BO_ATTACHMENT_FILE_CONTENT_TYPE);
184 		String fileContentBoField = request.getParameter(KRADConstants.BO_ATTACHMENT_FILE_CONTENT_FIELD);
185     	//require certain request properties
186     	if (fileName != null
187     			&& contentType != null
188     			&& fileContentBoField != null) {
189     		//make sure user has authorization to download attachment
190     		checkAuthorization(form, findMethodToCall(form, request));
191     		
192     		fileName = StringUtils.replace(fileName, " ", "_");
193     		
194     		InquiryForm inquiryForm = (InquiryForm) form;
195         	BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
196     		checkBO(bo);
197     		
198     		Class clazz = (bo.getClass());
199     		Method method = clazz.getMethod("get"+StringUtils.capitalize(fileContentBoField));
200     		byte[] fileContents = (byte[]) method.invoke(bo);
201     		streamToResponse(fileContents, fileName, contentType,response);
202     	} else {
203     		if (fileName == null) {
204     			LOG.error("Request Parameter \""+ KRADConstants.BO_ATTACHMENT_FILE_NAME + "\" not provided.");
205     		}
206     		if (contentType == null) {
207     			LOG.error("Request Parameter \""+ KRADConstants.BO_ATTACHMENT_FILE_CONTENT_TYPE + "\" not provided.");
208     		}
209     		if (fileContentBoField == null) {
210     			LOG.error("Request Parameter \""+ KRADConstants.BO_ATTACHMENT_FILE_CONTENT_FIELD + "\" not provided.");
211     		}
212     	}
213     	return null;
214     }
215     
216     
217     /**
218      * Downloads the selected attachment to the user's browser
219      *
220      * @param mapping
221      * @param form
222      * @param request
223      * @param response
224      * @return ActionForward
225      * @throws Exception
226      */
227     public ActionForward downloadBOAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
228         Long noteIdentifier = Long.valueOf(request.getParameter(KRADConstants.NOTE_IDENTIFIER));
229 
230         Note note = this.getNoteService().getNoteByNoteId(noteIdentifier);
231         if(note != null){
232         	Attachment attachment = note.getAttachment();
233         	if(attachment != null){
234         		//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)
235         		attachment.setNote(note);
236         		WebUtils.saveMimeInputStreamAsFile(response, attachment.getAttachmentMimeTypeCode(), attachment.getAttachmentContents(), attachment.getAttachmentFileName(), attachment.getAttachmentFileSize().intValue());
237         	}
238         	return null;
239         }
240         
241         return mapping.findForward(RiceConstants.MAPPING_BASIC);
242     }
243     
244     public ActionForward continueWithInquiry(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
245     	InquiryForm inquiryForm = (InquiryForm) form;
246     	
247     	if (inquiryForm.getBusinessObjectClassName() == null) {
248     		LOG.error("Business object name not given.");
249     		throw new RuntimeException("Business object name not given.");
250     	}
251     	
252         BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
253         checkBO(bo);
254         
255         populateSections(mapping, request, inquiryForm, bo);
256         
257         return mapping.findForward(RiceConstants.MAPPING_BASIC);
258     }
259     
260     /**
261      * Turns on (or off) the inactive record display for a maintenance collection.
262      */
263     public ActionForward toggleInactiveRecordDisplay(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
264         InquiryForm inquiryForm = (InquiryForm) form;
265         if (inquiryForm.getBusinessObjectClassName() == null) {
266             LOG.error("Business object name not given.");
267             throw new RuntimeException("Business object name not given.");
268         }
269         
270         BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
271         checkBO(bo);
272         
273         Inquirable kualiInquirable = inquiryForm.getInquirable();
274         //////////////////////////////
275         String collectionName = extractCollectionName(request, KRADConstants.TOGGLE_INACTIVE_METHOD);
276         if (collectionName == null) {
277             LOG.error("Unable to get find collection name in request.");
278             throw new RuntimeException("Unable to get find collection class in request.");
279         }  
280         String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
281         boolean showInactive = Boolean.parseBoolean(StringUtils.substringBetween(parameterName, KRADConstants.METHOD_TO_CALL_BOPARM_LEFT_DEL, "."));
282         kualiInquirable.setShowInactiveRecords(collectionName, showInactive);
283         //////////////////////////////
284         
285         populateSections(mapping, request, inquiryForm, bo);
286         
287         if (showInactive) {
288         	reopenInactiveRecords(inquiryForm, collectionName);
289         }
290         
291         return mapping.findForward(RiceConstants.MAPPING_BASIC);
292     }
293     
294     /**
295      * Attempts to reopen sub tabs which would have been closed for inactive records
296      * 
297      * @param inquiryForm the form to reopen records on
298      * @param collectionName the name of the collection reopening
299      */
300     protected void reopenInactiveRecords(InquiryForm inquiryForm, String collectionName) {
301     	for (Object sectionAsObject : inquiryForm.getSections()) {
302     		final Section section = (Section)sectionAsObject;
303     		for (Row row: section.getRows()) {
304     			for (Field field : row.getFields()) {
305     				if (field.getFieldType().equals(Field.CONTAINER) && field.getContainerName().startsWith(collectionName)) {
306     					final String tabKey = WebUtils.generateTabKey(generateCollectionSubTabName(field));
307     					inquiryForm.getTabStates().put(tabKey, "OPEN");
308     				}
309     			}
310     		}
311     	}
312     }
313     
314     /**
315      * Finds a container field's sub tab name
316      * 
317      * @param field the collection sub tab name to  
318      * @return the sub tab name
319      */
320     protected String generateCollectionSubTabName(Field field) {
321     	final String containerName = field.getContainerElementName();
322     	final String cleanedContainerName = 
323     		(containerName == null) ?
324     				"" :
325     				containerName.replaceAll("\\d+", "");
326     	StringBuilder subTabName = new StringBuilder(cleanedContainerName);
327     	for (Field containerField : field.getContainerDisplayFields()) {
328     		subTabName.append(containerField.getPropertyValue());
329     	}
330     	return subTabName.toString();
331     }
332     
333     @Override
334     public ActionForward toggleTab(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
335         InquiryForm inquiryForm = (InquiryForm) form;
336         if (inquiryForm.getBusinessObjectClassName() == null) {
337             LOG.error("Business object name not given.");
338             throw new RuntimeException("Business object name not given.");
339         }
340         
341         BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
342         checkBO(bo);
343         
344         populateSections(mapping, request, inquiryForm, bo);
345         
346         Inquirable kualiInquirable = inquiryForm.getInquirable();
347         
348         return super.toggleTab(mapping, form, request, response);
349     }
350     
351     
352     
353     /**
354 	 * @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)
355 	 */
356 	@Override
357 	public ActionForward hideAllTabs(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
358         // KULRICE-2852: Overrides hideAllTabs() so that it also calls the populateSections() method.
359 		InquiryForm inquiryForm = (InquiryForm) form;
360         if (inquiryForm.getBusinessObjectClassName() == null) {
361             LOG.error("Business object name not given.");
362             throw new RuntimeException("Business object name not given.");
363         }
364         
365         BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
366         checkBO(bo);
367         
368         populateSections(mapping, request, inquiryForm, bo);
369 		
370 		return super.hideAllTabs(mapping, form, request, response);
371 	}
372 
373 	/**
374 	 * @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)
375 	 */
376 	@Override
377 	public ActionForward showAllTabs(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
378         // KULRICE-2852: Overrides showAllTabs() so that it also calls the populateSections() method.
379 		InquiryForm inquiryForm = (InquiryForm) form;
380         if (inquiryForm.getBusinessObjectClassName() == null) {
381             LOG.error("Business object name not given.");
382             throw new RuntimeException("Business object name not given.");
383         }
384         
385         BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
386         checkBO(bo);
387         
388         populateSections(mapping, request, inquiryForm, bo);
389 		
390 		return super.showAllTabs(mapping, form, request, response);
391 	}
392 
393 	/**
394      * Handles exporting the BusinessObject for this Inquiry to XML if it has a custom XML exporter available.
395      */
396     public ActionForward export(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
397     	InquiryForm inquiryForm = (InquiryForm) form;
398     	if (inquiryForm.isCanExport()) {
399     		BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
400     		checkBO(bo);
401     		if (bo != null) {
402 	    		BusinessObjectEntry businessObjectEntry = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(inquiryForm.getBusinessObjectClassName());
403 	    		Class<? extends Exporter> exporterClass = businessObjectEntry.getExporterClass();
404 	    		if (exporterClass != null) {
405 	    			Exporter exporter = exporterClass.newInstance();
406 	        		response.setContentType(KRADConstants.XML_MIME_TYPE);
407 	        		response.setHeader("Content-disposition", "attachment; filename=export.xml");
408 	        		exporter.export(businessObjectEntry.getBusinessObjectClass(), Collections.singletonList(bo), KRADConstants.XML_FORMAT, response.getOutputStream());
409 	        	}
410     		} else {
411     			//show the empty section with error
412     			populateSections(mapping, request, inquiryForm, bo);
413     			return mapping.findForward(RiceConstants.MAPPING_BASIC); 
414     		}
415         }
416         
417         return null;
418     }
419 
420     /**
421      * Convert a Request into a Map<String,String>. Technically, Request parameters do not neatly translate into a Map of Strings,
422      * because a given parameter may legally appear more than once (so a Map of String[] would be more accurate.) This method should
423      * be safe for business objects, but may not be reliable for more general uses.
424      */
425     protected String extractCollectionName(HttpServletRequest request, String methodToCall) {
426         // collection name and underlying object type from request parameter
427         String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
428         String collectionName = null;
429         if (StringUtils.isNotBlank(parameterName)) {
430             collectionName = StringUtils.substringBetween(parameterName, methodToCall + ".", ".(");
431         }
432         return collectionName;
433     }
434     
435     protected BusinessObject retrieveBOFromInquirable(InquiryForm inquiryForm) {
436     	Inquirable kualiInquirable = inquiryForm.getInquirable();
437         // retrieve the business object
438         BusinessObject bo = kualiInquirable.getBusinessObject(inquiryForm.retrieveInquiryDecryptedPrimaryKeys());
439         if (bo == null) {
440             LOG.error("No records found in inquiry action.");
441             GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS, RiceKeyConstants.ERROR_INQUIRY);
442         }
443         return bo;
444     }
445     
446     protected void populateSections(ActionMapping mapping, HttpServletRequest request, InquiryForm inquiryForm, BusinessObject bo) {
447     	Inquirable kualiInquirable = inquiryForm.getInquirable();
448     	
449     	if (bo != null) {
450     		// get list of populated sections for display
451     		List<Section> sections = kualiInquirable.getSections(bo);
452         	inquiryForm.setSections(sections);
453         	kualiInquirable.addAdditionalSections(sections, bo);
454     	} else {
455     		inquiryForm.setSections(getEmptySections(kualiInquirable.getTitle()));
456     	}
457 
458         request.setAttribute(KRADConstants.INQUIRABLE_ATTRIBUTE_NAME, kualiInquirable);
459     }
460     
461     /**
462     *
463     * Handy method to stream the byte array to response object
464     * @param attachmentDataSource
465     * @param response
466     * @throws Exception
467     */
468    protected void streamToResponse(byte[] fileContents, String fileName, String fileContentType,HttpServletResponse response) throws Exception{
469        ByteArrayOutputStream baos = null;
470        try{
471            baos = new ByteArrayOutputStream(fileContents.length);
472            baos.write(fileContents);
473            WebUtils.saveMimeOutputStreamAsFile(response, fileContentType, baos, fileName);
474        }finally{
475            try{
476                if(baos!=null){
477                    baos.close();
478                    baos = null;
479                }
480            }catch(IOException ioEx){
481                LOG.error("Error while downloading attachment");
482                throw new RuntimeException("IOException occurred while downloading attachment", ioEx);
483            }
484        }
485    }
486     /**
487      * Returns a section list with one empty section and one row.
488      * 
489      * @return list of sections
490      */
491     private List<Section> getEmptySections(String title) {
492     	final Row row = new Row(Collections.<Field>emptyList());
493     	
494     	final Section section = new Section(Collections.singletonList(row));
495 		section.setErrorKey("*");
496 		section.setSectionTitle(title != null ? title : "");
497 		section.setNumberOfColumns(0);
498 		
499 		return Collections.singletonList(section);
500     }
501     
502     /**
503      * throws an exception if BO fails the check.
504      * @param bo the BusinessObject to check.
505      * @throws UnsupportedOperationException if BO is null & no messages have been generated.
506      */
507     private void checkBO(BusinessObject bo) {
508         if (bo == null && GlobalVariables.getMessageMap().hasNoMessages()) {
509         	throw new UnsupportedOperationException("The record you have inquired on does not exist.");
510         }
511     }
512     
513     protected NoteService getNoteService() {
514 		if ( noteService == null ) {
515 			noteService = KRADServiceLocator.getNoteService();
516 		}
517 		return this.noteService;
518 	}
519 }