View Javadoc

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