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