001 /** 002 * Copyright 2005-2011 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.ken.web.spring; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.apache.log4j.Logger; 020 import org.kuali.rice.ken.bo.Notification; 021 import org.kuali.rice.ken.bo.NotificationChannelReviewer; 022 import org.kuali.rice.ken.document.kew.NotificationWorkflowDocument; 023 import org.kuali.rice.ken.service.NotificationMessageContentService; 024 import org.kuali.rice.ken.service.NotificationRecipientService; 025 import org.kuali.rice.ken.service.NotificationWorkflowDocumentService; 026 import org.kuali.rice.ken.util.NotificationConstants; 027 import org.kuali.rice.ken.util.Util; 028 import org.kuali.rice.kew.api.WorkflowDocument; 029 import org.kuali.rice.kew.api.WorkflowDocumentFactory; 030 import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes; 031 import org.kuali.rice.kim.api.identity.Person; 032 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 033 import org.springframework.validation.BindException; 034 import org.springframework.validation.ValidationUtils; 035 import org.springframework.web.bind.ServletRequestBindingException; 036 import org.springframework.web.servlet.ModelAndView; 037 import org.springframework.web.servlet.mvc.multiaction.MultiActionController; 038 039 import javax.servlet.ServletException; 040 import javax.servlet.http.HttpServletRequest; 041 import javax.servlet.http.HttpServletResponse; 042 import java.util.Date; 043 import java.util.HashMap; 044 import java.util.List; 045 import java.util.Map; 046 047 048 /** 049 * Implements reviewer Approve/Disapprove and initiator Acknowledge of a Notification requests 050 * sent to channels configured with reviewers 051 * @author Kuali Rice Team (rice.collab@kuali.org) 052 */ 053 public class AdministerNotificationRequestController extends MultiActionController { 054 private static final Logger LOG = Logger.getLogger(AdministerNotificationRequestController.class); 055 056 /** 057 * Command object for this controller 058 */ 059 public static class AdministerNotificationRequestCommand { 060 // incoming 061 private String docId; 062 063 // outgoing 064 private WorkflowDocument document; 065 private Notification notification; 066 private String renderedContent; 067 private boolean valid = true; 068 private String message; 069 070 public String getDocId() { 071 return docId; 072 } 073 public void setDocId(String docId) { 074 this.docId = docId; 075 } 076 public WorkflowDocument getDocument() { 077 return document; 078 } 079 public void setDocument(WorkflowDocument document) { 080 this.document = document; 081 } 082 public Notification getNotification() { 083 return notification; 084 } 085 public void setNotification(Notification notification) { 086 this.notification = notification; 087 } 088 public String getRenderedContent() { 089 return renderedContent; 090 } 091 public void setRenderedContent(String renderedContent) { 092 this.renderedContent = renderedContent; 093 } 094 public boolean isValid() { 095 return valid; 096 } 097 public void setValid(boolean valid) { 098 this.valid = valid; 099 } 100 public String getMessage() { 101 return message; 102 } 103 public void setMessage(String message) { 104 this.message = message; 105 } 106 } 107 108 protected NotificationMessageContentService messageContentService; 109 protected NotificationWorkflowDocumentService workflowDocumentService; 110 protected NotificationRecipientService recipientService; 111 112 /** 113 * Sets the messageContentService attribute value. 114 * @param messageContentService the NotificationMessageContentService impl 115 */ 116 public void setMessageContentService( 117 NotificationMessageContentService notificationMessageContentService) { 118 this.messageContentService = notificationMessageContentService; 119 } 120 121 /** 122 * Sets the workflowDocumentService attribute value. 123 * @param workflowDocumentService the NotificationWorkflowDocumentService impl 124 */ 125 public void setWorkflowDocumentService( 126 NotificationWorkflowDocumentService notificationWorkflowDocumentService) { 127 this.workflowDocumentService = notificationWorkflowDocumentService; 128 } 129 130 /** 131 * Sets the recipientService attribute value. 132 * @param recipientService the NotificationRecipientService impl 133 */ 134 public void setRecipientService( 135 NotificationRecipientService notificationRecipientService) { 136 this.recipientService = notificationRecipientService; 137 } 138 139 /** 140 * Parses the serialized Notification xml from the workflow document application content into a reconstituted 141 * Notification BO 142 * @param document the WorkflowDocument 143 * @return a Notification BO reconstituted from the serialized XML form in the workflow document 144 * @throws Exception if parsing fails 145 */ 146 private Notification retrieveNotificationForWorkflowDocument(WorkflowDocument document) throws Exception { 147 String notificationAsXml = document.getApplicationContent(); 148 149 //parse out the application content into a Notification BO 150 Notification notification = messageContentService.parseSerializedNotificationXml(notificationAsXml.getBytes()); 151 152 return notification; 153 } 154 155 /** 156 * View action that displays an approve/disapprove/acknowledge view 157 * @param request the HttpServletRequest 158 * @param response the HttpServletResponse 159 * @param command the command object bound for this MultiActionController 160 * @return a view ModelAndView 161 */ 162 public ModelAndView view(HttpServletRequest request, HttpServletResponse response, AdministerNotificationRequestCommand command) { 163 // obtain a workflow user object first 164 String initiatorId = request.getRemoteUser(); 165 166 // now construct the workflow document, which will interact with workflow 167 if (command.getDocId() == null) { 168 throw new RuntimeException("An invalid document ID was recieved from KEW's action list."); 169 } 170 171 //check to see which view is being passed to us from the notification list - pop up or inline 172 String view = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.COMMAND); 173 String standaloneWindow = "true"; 174 if(view != null && view.equals(NotificationConstants.NOTIFICATION_DETAIL_VIEWS.INLINE)) { 175 standaloneWindow = "false"; 176 } 177 178 WorkflowDocument document; 179 Map<String, Object> model = new HashMap<String, Object>(); 180 // set into model whether we are dealing with a pop up or an inline window 181 model.put(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW, standaloneWindow); 182 try { 183 document = NotificationWorkflowDocument.loadNotificationDocument(initiatorId, command.getDocId()); 184 185 Notification notification = retrieveNotificationForWorkflowDocument(document); 186 187 // set up model 188 command.setDocument(document); 189 command.setNotification(notification); 190 // render the event content according to registered XSLT stylesheet 191 command.setRenderedContent(Util.transformContent(notification)); 192 193 LOG.info("notification auto remove date time: " + notification.getAutoRemoveDateTime()); 194 if (document.isApproved()) { 195 command.setValid(false); 196 command.setMessage("This notification request has been approved."); 197 } else if (document.isDisapproved()) { 198 command.setMessage("This notification request has been disapproved."); 199 } else if (notification.getAutoRemoveDateTime() != null && notification.getAutoRemoveDateTime().before(new Date(System.currentTimeMillis()))) { 200 /*if (!document.stateIsCanceled()) { 201 workflowDocumentService.terminateWorkflowDocument(new WorkflowDocument(new NetworkIdVO("notsys"), new Long(command.getDocId()))); 202 }*/ 203 // the autoremove date time has already passed...this notification request is null and void at this time 204 boolean disapproved = document.isDisapproved(); 205 if (!document.isDisapproved()) { 206 List<NotificationChannelReviewer> reviewers = notification.getChannel().getReviewers(); 207 String user = null; 208 for (NotificationChannelReviewer reviewer: reviewers) { 209 if (KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.equals(reviewer.getReviewerType())) { 210 if (reviewer.getReviewerId().equals(request.getRemoteUser())) { 211 user = request.getRemoteUser(); 212 } 213 } else if (KimGroupMemberTypes.GROUP_MEMBER_TYPE.equals(reviewer.getReviewerType())) { 214 // if it's a group 215 String[] members = recipientService.getGroupMembers(reviewer.getReviewerId()); 216 for (String member: members) { 217 if (StringUtils.equals(member, request.getRemoteUser())) { 218 user = request.getRemoteUser(); 219 break; 220 } 221 } 222 } 223 } 224 // if the current user is a reviewer, then disapprove as that user 225 if (user != null) { 226 WorkflowDocumentFactory.loadDocument(user, command.getDocId()).disapprove("Disapproving notification request. Auto-remove datetime has already passed."); 227 disapproved = true; 228 } 229 } 230 command.setValid(false); 231 if (disapproved) { 232 command.setMessage("This notification request is no longer valid because the Auto-Remove date has already passed. It has been disapproved. Please refresh your action list."); 233 } else { 234 command.setMessage("This notification request is no longer valid because the Auto-Remove date has already passed."); 235 } 236 } 237 238 model.put(getCommandName(command), command); 239 } catch (Exception e) { 240 throw new RuntimeException(e); 241 } 242 243 return new ModelAndView("ViewNotificationRequestDetails", model); 244 } 245 246 /** 247 * Approve action that approves a notification request 248 * @param request the HttpServletRequest 249 * @param response the HttpServletResponse 250 * @param command the command object bound for this MultiActionController 251 * @return a view ModelAndView 252 * @throws ServletException if an error occurs during approval 253 */ 254 public ModelAndView approve(HttpServletRequest request, HttpServletResponse response, AdministerNotificationRequestCommand command) throws ServletException { 255 administerEventNotificationMessage(request, response, command, "approve"); 256 Map<String, Object> model = new HashMap<String, Object>(); 257 model.put("workflowActionTaken", "Approved"); 258 model.put(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW, request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW)); 259 return new ModelAndView("SendNotificationRequestActionTakenWindow", model); 260 } 261 262 /** 263 * Disapprove action that disapproves a notification request 264 * @param request the HttpServletRequest 265 * @param response the HttpServletResponse 266 * @param command the command object bound for this MultiActionController 267 * @return a view ModelAndView 268 * @throws ServletException if an error occurs during disapproval 269 */ 270 public ModelAndView disapprove(HttpServletRequest request, HttpServletResponse response, AdministerNotificationRequestCommand command) throws ServletException { 271 administerEventNotificationMessage(request, response, command, "disapprove"); 272 Map<String, Object> model = new HashMap<String, Object>(); 273 model.put("workflowActionTaken", "Disapproved"); 274 model.put(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW, request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW)); 275 return new ModelAndView("SendNotificationRequestActionTakenWindow", model); 276 } 277 278 /** 279 * Acknowledge action that acknowledges a notification request disapproval 280 * @param request the HttpServletRequest 281 * @param response the HttpServletResponse 282 * @param command the command object bound for this MultiActionController 283 * @return a view ModelAndView 284 * @throws ServletException if an error occurs during acknowledgement 285 */ 286 public ModelAndView acknowledge(HttpServletRequest request, HttpServletResponse response, AdministerNotificationRequestCommand command) throws ServletException { 287 administerEventNotificationMessage(request, response, command, "acknowledge"); 288 Map<String, Object> model = new HashMap<String, Object>(); 289 model.put(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW, request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW)); 290 model.put("workflowActionTaken", "Acknowledged"); 291 return new ModelAndView("SendNotificationRequestActionTakenWindow", model); 292 } 293 294 /** 295 * This method handles approval/disapproval/acknowledgement of the notification request 296 * @param request the HttpServletRequest 297 * @param response the HttpServletResponse 298 * @param command the command object bound for this MultiActionController 299 * @throws ServletException 300 */ 301 private void administerEventNotificationMessage(HttpServletRequest request, HttpServletResponse response, AdministerNotificationRequestCommand command, String action) throws ServletException { 302 LOG.debug("remoteUser: " + request.getRemoteUser()); 303 304 BindException bindException = new BindException(command, "command"); 305 ValidationUtils.rejectIfEmpty(bindException, "docId", "Document id must be specified"); 306 if (bindException.hasErrors()) { 307 throw new ServletRequestBindingException("Document id must be specified", bindException); 308 } 309 310 // obtain a workflow user object first 311 //WorkflowIdDTO user = new WorkflowIdDTO(request.getRemoteUser()); 312 String userId = request.getRemoteUser(); 313 314 try { 315 // now construct the workflow document, which will interact with workflow 316 WorkflowDocument document = NotificationWorkflowDocument.loadNotificationDocument(userId, command.getDocId()); 317 318 Notification notification = retrieveNotificationForWorkflowDocument(document); 319 320 String initiatorPrincipalId = document.getInitiatorPrincipalId(); 321 Person initiator = KimApiServiceLocator.getPersonService().getPerson(initiatorPrincipalId); 322 String notificationBlurb = notification.getContentType().getName() + " notification submitted by " + initiator.getName() + " for channel " + notification.getChannel().getName(); 323 if ("disapprove".equals(action)) { 324 document.disapprove("User " + userId + " disapproving " + notificationBlurb); 325 } else if ("approve".equals(action)) { 326 document.approve("User " + userId + " approving " + notificationBlurb); 327 } else if ("acknowledge".equals(action)) { 328 document.acknowledge("User " + userId + " acknowledging " + notificationBlurb); 329 } 330 } catch (Exception e) { 331 LOG.error("Exception occurred taking action on notification request", e); 332 throw new ServletException("Exception occurred taking action on notification request", e); 333 } 334 } 335 }