001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.kns.web.struts.action;
017
018 import org.apache.commons.collections.CollectionUtils;
019 import org.apache.commons.lang.StringUtils;
020 import org.apache.struts.action.ActionForm;
021 import org.apache.struts.action.ActionForward;
022 import org.apache.struts.action.ActionMapping;
023 import org.apache.struts.action.RedirectingActionForward;
024 import org.kuali.rice.core.api.util.RiceConstants;
025 import org.kuali.rice.core.api.util.RiceKeyConstants;
026 import org.kuali.rice.kim.api.KimConstants;
027 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
028 import org.kuali.rice.kns.document.MaintenanceDocumentBase;
029 import org.kuali.rice.kns.inquiry.Inquirable;
030 import org.kuali.rice.kns.util.WebUtils;
031 import org.kuali.rice.kns.web.struts.form.InquiryForm;
032 import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
033 import org.kuali.rice.kns.web.ui.Field;
034 import org.kuali.rice.kns.web.ui.Row;
035 import org.kuali.rice.kns.web.ui.Section;
036 import org.kuali.rice.krad.bo.Attachment;
037 import org.kuali.rice.krad.bo.BusinessObject;
038 import org.kuali.rice.krad.bo.DocumentAttachment;
039 import org.kuali.rice.krad.bo.Exporter;
040 import org.kuali.rice.krad.bo.MultiDocumentAttachment;
041 import org.kuali.rice.krad.bo.Note;
042 import org.kuali.rice.krad.bo.PersistableAttachment;
043 import org.kuali.rice.krad.bo.PersistableAttachmentList;
044 import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
045 import org.kuali.rice.krad.exception.AuthorizationException;
046 import org.kuali.rice.krad.service.KRADServiceLocator;
047 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
048 import org.kuali.rice.krad.service.ModuleService;
049 import org.kuali.rice.krad.service.NoteService;
050 import org.kuali.rice.krad.util.GlobalVariables;
051 import org.kuali.rice.krad.util.KRADConstants;
052 import org.kuali.rice.krad.util.KRADUtils;
053
054 import javax.servlet.http.HttpServletRequest;
055 import javax.servlet.http.HttpServletResponse;
056 import java.io.ByteArrayOutputStream;
057 import java.io.IOException;
058 import java.lang.reflect.Method;
059 import java.util.Collections;
060 import java.util.List;
061 import java.util.Map;
062
063 /**
064 * This class handles actions for inquiries of business objects.
065 */
066 public class KualiInquiryAction extends KualiAction {
067 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiInquiryAction.class);
068 private NoteService noteService;
069
070 @Override
071 protected void checkAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
072 if (!(form instanceof InquiryForm)) {
073 super.checkAuthorization(form, methodToCall);
074 } else {
075 try {
076 if(!KRADConstants.DOWNLOAD_BO_ATTACHMENT_METHOD.equals(methodToCall)){
077 Class businessObjectClass = Class.forName(((InquiryForm) form).getBusinessObjectClassName());
078 if (!KimApiServiceLocator.getPermissionService().isAuthorizedByTemplate(
079 GlobalVariables.getUserSession().getPrincipalId(), KRADConstants.KNS_NAMESPACE,
080 KimConstants.PermissionTemplateNames.INQUIRE_INTO_RECORDS,
081 KRADUtils.getNamespaceAndComponentSimpleName(businessObjectClass),
082 Collections.<String, String>emptyMap())) {
083
084 throw new AuthorizationException(GlobalVariables.getUserSession().getPerson().getPrincipalName(),
085 "inquire",
086 businessObjectClass.getSimpleName());
087 }
088 }
089 }
090 catch (ClassNotFoundException e) {
091 LOG.warn("Unable to load BusinessObject class: " + ((InquiryForm) form).getBusinessObjectClassName(), e);
092 super.checkAuthorization(form, methodToCall);
093 }
094 }
095 }
096
097 @Override
098 protected Map<String, String> getRoleQualification(ActionForm form,
099 String methodToCall) {
100 Map<String, String> roleQualification = super.getRoleQualification(
101 form, methodToCall);
102 if (form instanceof InquiryForm) {
103 Map<String, String> primaryKeys = ((InquiryForm) form)
104 .getInquiryPrimaryKeys();
105 if (primaryKeys != null) {
106 for (String keyName : primaryKeys.keySet()) {
107 roleQualification.put(keyName, primaryKeys.get(keyName));
108 }
109 }
110 }
111 return roleQualification;
112 }
113
114 @Override
115 public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
116 request.setAttribute(KRADConstants.PARAM_MAINTENANCE_VIEW_MODE, KRADConstants.PARAM_MAINTENANCE_VIEW_MODE_INQUIRY);
117 return super.execute(mapping, form, request, response);
118 }
119
120 /**
121 * Gets an inquirable impl from the impl service name parameter. Then calls lookup service to retrieve the record from the
122 * key/value parameters. Finally gets a list of Rows from the inquirable
123 */
124 public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
125 InquiryForm inquiryForm = (InquiryForm) form;
126 if (inquiryForm.getBusinessObjectClassName() == null) {
127 LOG.error("Business object name not given.");
128 throw new RuntimeException("Business object name not given.");
129 }
130
131 Class boClass = Class.forName(inquiryForm.getBusinessObjectClassName());
132 ModuleService responsibleModuleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(boClass);
133 if(responsibleModuleService!=null && responsibleModuleService.isExternalizable(boClass)){
134 String redirectUrl = responsibleModuleService.getExternalizableBusinessObjectInquiryUrl(boClass, (Map<String, String[]>) request.getParameterMap());
135 ActionForward redirectingActionForward = new RedirectingActionForward(redirectUrl);
136 redirectingActionForward.setModule("/");
137 return redirectingActionForward;
138 }
139
140 return continueWithInquiry(mapping, form, request, response);
141 }
142
143
144 /**
145 * Downloads the attachment to the user's browser
146 *
147 * @param mapping
148 * @param form
149 * @param request
150 * @param response
151 * @return ActionForward
152 * @throws Exception
153 */
154 public ActionForward downloadAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
155 InquiryForm inquiryForm = (InquiryForm) form;
156 int line = getSelectedLine(request);
157
158 BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
159 if (line < 0) {
160 if (bo instanceof PersistableAttachment) {
161 PersistableAttachment attachment = (PersistableAttachment)bo;
162 if (StringUtils.isNotBlank(attachment.getFileName())
163 && attachment.getAttachmentContent() != null) {
164 streamToResponse(attachment.getAttachmentContent(), attachment.getFileName(), attachment.getContentType(), response);
165 }
166 }
167 } else {
168 if (bo instanceof PersistableAttachmentList) {
169 PersistableAttachmentList<PersistableAttachment> attachmentsBo = (PersistableAttachmentList<PersistableAttachment>)bo;
170 if (CollectionUtils.isEmpty(attachmentsBo.getAttachments())) {
171 return null;
172 }
173
174 List<? extends PersistableAttachment> attachments = attachmentsBo.getAttachments();
175 if (CollectionUtils.isNotEmpty(attachments)
176 && attachments.size() > line) {
177 PersistableAttachment attachment = (PersistableAttachment)attachmentsBo.getAttachments().get(line);
178 streamToResponse(attachment.getAttachmentContent(), attachment.getFileName(), attachment.getContentType(), response);
179 }
180 }
181 }
182 return null;
183 }
184
185 public ActionForward downloadCustomBOAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
186 String fileName = request.getParameter(KRADConstants.BO_ATTACHMENT_FILE_NAME);
187 String contentType = request.getParameter(KRADConstants.BO_ATTACHMENT_FILE_CONTENT_TYPE);
188 String fileContentBoField = request.getParameter(KRADConstants.BO_ATTACHMENT_FILE_CONTENT_FIELD);
189 //require certain request properties
190 if (fileName != null
191 && contentType != null
192 && fileContentBoField != null) {
193 //make sure user has authorization to download attachment
194 checkAuthorization(form, findMethodToCall(form, request));
195
196 fileName = StringUtils.replace(fileName, " ", "_");
197
198 InquiryForm inquiryForm = (InquiryForm) form;
199 BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
200 checkBO(bo);
201
202 Class clazz = (bo.getClass());
203 Method method = clazz.getMethod("get"+StringUtils.capitalize(fileContentBoField));
204 byte[] fileContents = (byte[]) method.invoke(bo);
205 streamToResponse(fileContents, fileName, contentType,response);
206 } else {
207 if (fileName == null) {
208 LOG.error("Request Parameter \""+ KRADConstants.BO_ATTACHMENT_FILE_NAME + "\" not provided.");
209 }
210 if (contentType == null) {
211 LOG.error("Request Parameter \""+ KRADConstants.BO_ATTACHMENT_FILE_CONTENT_TYPE + "\" not provided.");
212 }
213 if (fileContentBoField == null) {
214 LOG.error("Request Parameter \""+ KRADConstants.BO_ATTACHMENT_FILE_CONTENT_FIELD + "\" not provided.");
215 }
216 }
217 return null;
218 }
219
220
221 /**
222 * Downloads the selected attachment to the user's browser
223 *
224 * @param mapping
225 * @param form
226 * @param request
227 * @param response
228 * @return ActionForward
229 * @throws Exception
230 */
231 public ActionForward downloadBOAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
232 Long noteIdentifier = Long.valueOf(request.getParameter(KRADConstants.NOTE_IDENTIFIER));
233
234 Note note = this.getNoteService().getNoteByNoteId(noteIdentifier);
235 if(note != null){
236 Attachment attachment = note.getAttachment();
237 if(attachment != null){
238 //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)
239 attachment.setNote(note);
240 WebUtils.saveMimeInputStreamAsFile(response, attachment.getAttachmentMimeTypeCode(), attachment.getAttachmentContents(), attachment.getAttachmentFileName(), attachment.getAttachmentFileSize().intValue());
241 }
242 return null;
243 }
244
245 return mapping.findForward(RiceConstants.MAPPING_BASIC);
246 }
247
248 public ActionForward continueWithInquiry(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
249 InquiryForm inquiryForm = (InquiryForm) form;
250
251 if (inquiryForm.getBusinessObjectClassName() == null) {
252 LOG.error("Business object name not given.");
253 throw new RuntimeException("Business object name not given.");
254 }
255
256 BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
257 checkBO(bo);
258
259 populateSections(mapping, request, inquiryForm, bo);
260
261 return mapping.findForward(RiceConstants.MAPPING_BASIC);
262 }
263
264 /**
265 * Turns on (or off) the inactive record display for a maintenance collection.
266 */
267 public ActionForward toggleInactiveRecordDisplay(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
268 InquiryForm inquiryForm = (InquiryForm) form;
269 if (inquiryForm.getBusinessObjectClassName() == null) {
270 LOG.error("Business object name not given.");
271 throw new RuntimeException("Business object name not given.");
272 }
273
274 BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
275 checkBO(bo);
276
277 Inquirable kualiInquirable = inquiryForm.getInquirable();
278 //////////////////////////////
279 String collectionName = extractCollectionName(request, KRADConstants.TOGGLE_INACTIVE_METHOD);
280 if (collectionName == null) {
281 LOG.error("Unable to get find collection name in request.");
282 throw new RuntimeException("Unable to get find collection class in request.");
283 }
284 String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
285 boolean showInactive = Boolean.parseBoolean(StringUtils.substringBetween(parameterName, KRADConstants.METHOD_TO_CALL_BOPARM_LEFT_DEL, "."));
286 kualiInquirable.setShowInactiveRecords(collectionName, showInactive);
287 //////////////////////////////
288
289 populateSections(mapping, request, inquiryForm, bo);
290
291 if (showInactive) {
292 reopenInactiveRecords(inquiryForm, collectionName);
293 }
294
295 return mapping.findForward(RiceConstants.MAPPING_BASIC);
296 }
297
298 /**
299 * Attempts to reopen sub tabs which would have been closed for inactive records
300 *
301 * @param inquiryForm the form to reopen records on
302 * @param collectionName the name of the collection reopening
303 */
304 protected void reopenInactiveRecords(InquiryForm inquiryForm, String collectionName) {
305 for (Object sectionAsObject : inquiryForm.getSections()) {
306 final Section section = (Section)sectionAsObject;
307 for (Row row: section.getRows()) {
308 for (Field field : row.getFields()) {
309 if (field.getFieldType().equals(Field.CONTAINER) && field.getContainerName().startsWith(collectionName)) {
310 final String tabKey = WebUtils.generateTabKey(generateCollectionSubTabName(field));
311 inquiryForm.getTabStates().put(tabKey, "OPEN");
312 }
313 }
314 }
315 }
316 }
317
318 /**
319 * Finds a container field's sub tab name
320 *
321 * @param field the collection sub tab name to
322 * @return the sub tab name
323 */
324 protected String generateCollectionSubTabName(Field field) {
325 final String containerName = field.getContainerElementName();
326 final String cleanedContainerName =
327 (containerName == null) ?
328 "" :
329 containerName.replaceAll("\\d+", "");
330 StringBuilder subTabName = new StringBuilder(cleanedContainerName);
331 for (Field containerField : field.getContainerDisplayFields()) {
332 subTabName.append(containerField.getPropertyValue());
333 }
334 return subTabName.toString();
335 }
336
337 @Override
338 public ActionForward toggleTab(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
339 InquiryForm inquiryForm = (InquiryForm) form;
340 if (inquiryForm.getBusinessObjectClassName() == null) {
341 LOG.error("Business object name not given.");
342 throw new RuntimeException("Business object name not given.");
343 }
344
345 BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
346 checkBO(bo);
347
348 populateSections(mapping, request, inquiryForm, bo);
349
350 Inquirable kualiInquirable = inquiryForm.getInquirable();
351
352 return super.toggleTab(mapping, form, request, response);
353 }
354
355
356
357 /**
358 * @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)
359 */
360 @Override
361 public ActionForward hideAllTabs(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
362 // KULRICE-2852: Overrides hideAllTabs() so that it also calls the populateSections() method.
363 InquiryForm inquiryForm = (InquiryForm) form;
364 if (inquiryForm.getBusinessObjectClassName() == null) {
365 LOG.error("Business object name not given.");
366 throw new RuntimeException("Business object name not given.");
367 }
368
369 BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
370 checkBO(bo);
371
372 populateSections(mapping, request, inquiryForm, bo);
373
374 return super.hideAllTabs(mapping, form, request, response);
375 }
376
377 /**
378 * @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)
379 */
380 @Override
381 public ActionForward showAllTabs(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
382 // KULRICE-2852: Overrides showAllTabs() so that it also calls the populateSections() method.
383 InquiryForm inquiryForm = (InquiryForm) form;
384 if (inquiryForm.getBusinessObjectClassName() == null) {
385 LOG.error("Business object name not given.");
386 throw new RuntimeException("Business object name not given.");
387 }
388
389 BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
390 checkBO(bo);
391
392 populateSections(mapping, request, inquiryForm, bo);
393
394 return super.showAllTabs(mapping, form, request, response);
395 }
396
397 /**
398 * Handles exporting the BusinessObject for this Inquiry to XML if it has a custom XML exporter available.
399 */
400 public ActionForward export(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
401 InquiryForm inquiryForm = (InquiryForm) form;
402 if (inquiryForm.isCanExport()) {
403 BusinessObject bo = retrieveBOFromInquirable(inquiryForm);
404 checkBO(bo);
405 if (bo != null) {
406 BusinessObjectEntry businessObjectEntry = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(inquiryForm.getBusinessObjectClassName());
407 Class<? extends Exporter> exporterClass = businessObjectEntry.getExporterClass();
408 if (exporterClass != null) {
409 Exporter exporter = exporterClass.newInstance();
410 response.setContentType(KRADConstants.XML_MIME_TYPE);
411 response.setHeader("Content-disposition", "attachment; filename=export.xml");
412 exporter.export(businessObjectEntry.getBusinessObjectClass(), Collections.singletonList(bo), KRADConstants.XML_FORMAT, response.getOutputStream());
413 }
414 } else {
415 //show the empty section with error
416 populateSections(mapping, request, inquiryForm, bo);
417 return mapping.findForward(RiceConstants.MAPPING_BASIC);
418 }
419 }
420
421 return null;
422 }
423
424 /**
425 * Convert a Request into a Map<String,String>. Technically, Request parameters do not neatly translate into a Map of Strings,
426 * because a given parameter may legally appear more than once (so a Map of String[] would be more accurate.) This method should
427 * be safe for business objects, but may not be reliable for more general uses.
428 */
429 protected String extractCollectionName(HttpServletRequest request, String methodToCall) {
430 // collection name and underlying object type from request parameter
431 String parameterName = (String) request.getAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE);
432 String collectionName = null;
433 if (StringUtils.isNotBlank(parameterName)) {
434 collectionName = StringUtils.substringBetween(parameterName, methodToCall + ".", ".(");
435 }
436 return collectionName;
437 }
438
439 protected BusinessObject retrieveBOFromInquirable(InquiryForm inquiryForm) {
440 Inquirable kualiInquirable = inquiryForm.getInquirable();
441 // retrieve the business object
442 BusinessObject bo = kualiInquirable.getBusinessObject(inquiryForm.retrieveInquiryDecryptedPrimaryKeys());
443 if (bo == null) {
444 LOG.error("No records found in inquiry action.");
445 GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS, RiceKeyConstants.ERROR_INQUIRY);
446 }
447 return bo;
448 }
449
450 protected void populateSections(ActionMapping mapping, HttpServletRequest request, InquiryForm inquiryForm, BusinessObject bo) {
451 Inquirable kualiInquirable = inquiryForm.getInquirable();
452
453 if (bo != null) {
454 // get list of populated sections for display
455 List<Section> sections = kualiInquirable.getSections(bo);
456 inquiryForm.setSections(sections);
457 kualiInquirable.addAdditionalSections(sections, bo);
458 } else {
459 inquiryForm.setSections(getEmptySections(kualiInquirable.getTitle()));
460 }
461
462 request.setAttribute(KRADConstants.INQUIRABLE_ATTRIBUTE_NAME, kualiInquirable);
463 }
464
465 /**
466 *
467 * Handy method to stream the byte array to response object
468 * @param attachmentDataSource
469 * @param response
470 * @throws Exception
471 */
472 protected void streamToResponse(byte[] fileContents, String fileName, String fileContentType,HttpServletResponse response) throws Exception{
473 ByteArrayOutputStream baos = null;
474 try{
475 baos = new ByteArrayOutputStream(fileContents.length);
476 baos.write(fileContents);
477 WebUtils.saveMimeOutputStreamAsFile(response, fileContentType, baos, fileName);
478 }finally{
479 try{
480 if(baos!=null){
481 baos.close();
482 baos = null;
483 }
484 }catch(IOException ioEx){
485 LOG.error("Error while downloading attachment");
486 throw new RuntimeException("IOException occurred while downloading attachment", ioEx);
487 }
488 }
489 }
490 /**
491 * Returns a section list with one empty section and one row.
492 *
493 * @return list of sections
494 */
495 private List<Section> getEmptySections(String title) {
496 final Row row = new Row(Collections.<Field>emptyList());
497
498 final Section section = new Section(Collections.singletonList(row));
499 section.setErrorKey("*");
500 section.setSectionTitle(title != null ? title : "");
501 section.setNumberOfColumns(0);
502
503 return Collections.singletonList(section);
504 }
505
506 /**
507 * throws an exception if BO fails the check.
508 * @param bo the BusinessObject to check.
509 * @throws UnsupportedOperationException if BO is null & no messages have been generated.
510 */
511 private void checkBO(BusinessObject bo) {
512 if (bo == null && GlobalVariables.getMessageMap().hasNoMessages()) {
513 throw new UnsupportedOperationException("The record you have inquired on does not exist.");
514 }
515 }
516
517 protected NoteService getNoteService() {
518 if ( noteService == null ) {
519 noteService = KRADServiceLocator.getNoteService();
520 }
521 return this.noteService;
522 }
523 }