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