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 }