001/** 002 * Copyright 2005-2017 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 */ 016package org.kuali.rice.ken.web.spring; 017 018import java.io.IOException; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022 023import javax.servlet.ServletException; 024import javax.servlet.http.HttpServletRequest; 025import javax.servlet.http.HttpServletResponse; 026 027import org.apache.commons.lang.StringUtils; 028import org.apache.log4j.Logger; 029import org.kuali.rice.ken.api.KenApiConstants; 030import org.kuali.rice.ken.bo.NotificationBo; 031import org.kuali.rice.ken.bo.NotificationMessageDelivery; 032import org.kuali.rice.ken.bo.NotificationRecipientBo; 033import org.kuali.rice.ken.bo.NotificationSenderBo; 034import org.kuali.rice.ken.service.NotificationMessageDeliveryService; 035import org.kuali.rice.ken.service.NotificationService; 036import org.kuali.rice.ken.service.NotificationWorkflowDocumentService; 037import org.kuali.rice.ken.util.NotificationConstants; 038import org.kuali.rice.ken.util.Util; 039import org.kuali.rice.kew.api.KewApiConstants; 040import org.kuali.rice.kew.api.KewApiServiceLocator; 041import org.kuali.rice.kim.api.identity.principal.Principal; 042import org.kuali.rice.kim.api.services.KimApiServiceLocator; 043import org.kuali.rice.krad.UserSession; 044import org.kuali.rice.krad.util.KRADUtils; 045import org.springframework.web.servlet.ModelAndView; 046import org.springframework.web.servlet.mvc.multiaction.MultiActionController; 047 048 049/** 050 * This class is the controller for the basic notification related actions - viewing, etc. 051 * @author Kuali Rice Team (rice.collab@kuali.org) 052 */ 053public class NotificationController extends MultiActionController { 054 /** Logger for this class and subclasses */ 055 private static final Logger LOG = Logger.getLogger(NotificationController.class); 056 057 protected NotificationService notificationService; 058 protected NotificationWorkflowDocumentService notificationWorkflowDocService; 059 protected NotificationMessageDeliveryService messageDeliveryService; 060 061 /** 062 * Set the NotificationService 063 * @param notificationService 064 */ 065 public void setNotificationService(NotificationService notificationService) { 066 this.notificationService = notificationService; 067 } 068 069 /** 070 * This method sets the NotificationWorkflowDocumentService 071 * @param s 072 */ 073 public void setNotificationWorkflowDocumentService(NotificationWorkflowDocumentService s) { 074 this.notificationWorkflowDocService = s; 075 } 076 077 /** 078 * Sets the messageDeliveryService attribute value. 079 * @param messageDeliveryService The messageDeliveryService to set. 080 */ 081 public void setMessageDeliveryService(NotificationMessageDeliveryService messageDeliveryService) { 082 this.messageDeliveryService = messageDeliveryService; 083 } 084 085 /** 086 * Handles the display of the main home page in the system. 087 * @param request : a servlet request 088 * @param response : a servlet response 089 * @throws ServletException : an exception 090 * @throws IOException : an exception 091 * @return a ModelAndView object 092 */ 093 public ModelAndView displayHome(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 094 String view = "HomePage"; 095 LOG.debug("remoteUser: "+request.getRemoteUser()); 096 Map<String, Object> model = new HashMap<String, Object>(); 097 return new ModelAndView(view, model); 098 } 099 100 /** 101 * This method handles displaying the notifications that an individual sent. 102 * @param request 103 * @param response 104 * @return ModelAndView 105 * @throws ServletException 106 * @throws IOException 107 */ 108 public ModelAndView displayNotificationsSent(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 109 String view = "NotificationsSent"; 110 LOG.debug("remoteUser: "+request.getRemoteUser()); 111 Map<String, Object> model = new HashMap<String, Object>(); 112 model.put("userId", request.getRemoteUser()); 113 return new ModelAndView(view, model); 114 } 115 116 /** 117 * This method handles displaying the search screen. 118 * @param request 119 * @param response 120 * @return ModelAndView 121 * @throws ServletException 122 * @throws IOException 123 */ 124 public ModelAndView displaySearch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 125 String view = "Search"; 126 LOG.debug("remoteUser: "+request.getRemoteUser()); 127 Map<String, Object> model = new HashMap<String, Object>(); 128 return new ModelAndView(view, model); 129 } 130 131 /** 132 * This method displays the user lookup screen. 133 * @param request 134 * @param response 135 * @return 136 * @throws ServletException 137 * @throws IOException 138 */ 139 public ModelAndView displayLookupUsers(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 140 String view = "LookupUsers"; 141 LOG.debug("remoteUser: "+request.getRemoteUser()); 142 Map<String, Object> model = new HashMap<String, Object>(); 143 return new ModelAndView(view, model); 144 } 145 146 /** 147 * This method displays the workgroup lookup screen. 148 * @param request 149 * @param response 150 * @return 151 * @throws ServletException 152 * @throws IOException 153 */ 154 public ModelAndView displayLookupWorkgroups(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 155 String view = "LookupWorkgroups"; 156 LOG.debug("remoteUser: "+request.getRemoteUser()); 157 Map<String, Object> model = new HashMap<String, Object>(); 158 return new ModelAndView(view, model); 159 } 160 161 162 /** 163 * This method retrieves the NotificationMessageDelivery given an HttpServletRequest which 164 * may contain EITHER a message delivery id or a workflow doc id. Therefore, this is a 165 * "special case" for handling the workflow deliverer. 166 * @param request the incoming {@link HttpServletRequest} 167 * @return the {@link NotificationMessageDelivery} or null if not found 168 */ 169 protected NotificationMessageDelivery determineMessageFromRequest(HttpServletRequest request) { 170 /** 171 * We can get the NotificationMessageDelivery object given a workflow ID or a NotificationMessageDelivery 172 * Id. This method might be called either from a workflow action list or 173 * as a link from a message deliverer endpoint such as an email message. 174 */ 175 String messageDeliveryId = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.MSG_DELIVERY_ID); 176 String delivererId = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.DELIVERER_ID); 177 if (delivererId == null) { 178 delivererId = request.getParameter(KewApiConstants.DOCUMENT_ID_PARAMETER); 179 } 180 181 NotificationMessageDelivery messageDelivery; 182 if (messageDeliveryId != null) { // this means that the request came in not from the action list, but rather from a delivery end point 183 LOG.debug("Looking up notification with messageDeliveryId: "+messageDeliveryId); 184 try { 185 messageDelivery = messageDeliveryService.getNotificationMessageDelivery(new Long(messageDeliveryId)); 186 } catch (Exception e) { 187 throw new RuntimeException("Error getting message with id: " + messageDeliveryId, e); 188 } 189 } else if (delivererId != null) { // this means that the request was triggered via the action list 190 LOG.debug("Looking up notification with workflowId: "+delivererId); 191 try { 192 messageDelivery = messageDeliveryService.getNotificationMessageDeliveryByDelivererId(delivererId); 193 } catch (Exception e) { 194 LOG.error("Error getting message with from deliverer id: " + delivererId, e); 195 throw new RuntimeException("Error getting message with deliverer id: " + delivererId, e); 196 } 197 } else { 198 throw new RuntimeException("Neither message ('" + NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.MSG_DELIVERY_ID 199 + "') nor deliverer id ('" + NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.DELIVERER_ID + "') were specified in the request"); 200 } 201 202 return messageDelivery; 203 } 204 205 /** 206 * @param req the {@link HttpServletRequest} 207 * @return whether the incoming request was from the action list 208 */ 209 protected boolean requestIsFromKEW(HttpServletRequest req) { 210 return req.getParameter(KewApiConstants.DOCUMENT_ID_PARAMETER) != null; 211 } 212 213 /** 214 * This controller handles displaying the appropriate notification details for a specific record. 215 * @param request : a servlet request 216 * @param response : a servlet response 217 * @throws ServletException : an exception 218 * @throws IOException : an exception 219 * @return a ModelAndView object 220 */ 221 public ModelAndView displayNotificationDetail(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 222 String view = "NotificationDetail"; // default to full view 223 224 UserSession userSession = KRADUtils.getUserSessionFromRequest(request); 225 String principalId = ""; 226 if(userSession != null) { 227 principalId = userSession.getPrincipalId(); 228 if(StringUtils.isBlank(principalId)) { 229 String principalName = request.getRemoteUser(); 230 Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalName); 231 if(principal != null) { 232 principalId = principal.getPrincipalId(); 233 } else { 234 throw new RuntimeException("There is no principal for principalName " + principalName); 235 } 236 } 237 } 238 239 String command = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.COMMAND); 240 String standaloneWindow = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW); 241 242 NotificationMessageDelivery messageDelivery = determineMessageFromRequest(request); 243 // now get the notification from the message delivery object 244 NotificationBo notification = messageDelivery.getNotification(); 245 boolean actionable = false; 246 247 if (requestIsFromKEW(request)) { 248 // check to see if this was a standalone window by examining the command from KEW before setting it to INLINE to force an inline view 249 if(command != null && 250 (command.equals(NotificationConstants.NOTIFICATION_DETAIL_VIEWS.NORMAL_VIEW) || 251 command.equals(NotificationConstants.NOTIFICATION_DETAIL_VIEWS.DOC_SEARCH_VIEW))) { 252 standaloneWindow = "true"; 253 } 254 255 // we want all messages from the action list in line 256 command = NotificationConstants.NOTIFICATION_DETAIL_VIEWS.INLINE; 257 } 258 259 actionable = (principalId).equals(messageDelivery.getUserRecipientId()) && NotificationConstants.MESSAGE_DELIVERY_STATUS.DELIVERED.equals(messageDelivery.getMessageDeliveryStatus()); 260 261 String documentId = request.getParameter(KewApiConstants.DOCUMENT_ID_PARAMETER); 262 if(StringUtils.isNotBlank(documentId)) { 263 boolean authorized = KewApiServiceLocator.getWorkflowDocumentActionsService().isUserInRouteLog(documentId, principalId, false); 264 LOG.debug("User in route log = " + authorized); 265 if(!authorized) { 266 Map<String, String> permissionDetails = new HashMap<String, String>(); 267 permissionDetails.put(KenApiConstants.KIMTypes.Channel.CHANNEL_ID, notification.getChannel().getId().toString()); 268 Map<String, String> qualification = new HashMap<String, String>(); 269 authorized = KimApiServiceLocator.getPermissionService().isAuthorizedByTemplate(principalId, KenApiConstants.Namespaces.CODE, KenApiConstants.Permissions.VIEW_NOTIFICATION, permissionDetails, qualification); 270 LOG.debug("User has 'View Notification' permission = " + authorized); 271 if(!authorized) { 272 return new ModelAndView("NotAuthorized"); 273 } 274 } 275 } 276 277 List<NotificationSenderBo> senders = notification.getSenders(); 278 List<NotificationRecipientBo> recipients = notification.getRecipients(); 279 280 String contenthtml = Util.transformContent(notification); 281 282 // check to see if the details need to be rendered in line (no stuff around them) 283 if (command != null && command.equals(NotificationConstants.NOTIFICATION_DETAIL_VIEWS.INLINE)) { 284 view = "NotificationDetailInline"; 285 } 286 287 Map<String, Object> model = new HashMap<String, Object>(); 288 model.put("notification", notification); 289 model.put("senders", senders); 290 model.put("recipients", recipients); 291 model.put("contenthtml", contenthtml); 292 model.put("messageDeliveryId", messageDelivery.getId()); 293 model.put("command", command); 294 model.put("actionable", actionable); 295 model.put(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW, standaloneWindow); 296 return new ModelAndView(view, model); 297 } 298 299 /** 300 * This method handles user dismissal of a message 301 * @param request : a servlet request 302 * @param response : a servlet response 303 * @return a ModelAndView object 304 */ 305 public ModelAndView dismissMessage(HttpServletRequest request, HttpServletResponse response) { 306 String command = request.getParameter("action"); 307 if (command == null) throw new RuntimeException("Dismissal command not specified"); 308 309 if (NotificationConstants.ACK_CAUSE.equals(command)) { 310 return dismissMessage(command, "Notificaton acknowledged. Please refresh your action list.", request, response); 311 } else if (NotificationConstants.FYI_CAUSE.equals(command)) { 312 return dismissMessage(command, "Action Taken. Please refresh your action list.", request, response); 313 } else { 314 throw new RuntimeException("Unknown dismissal command: " + command); 315 } 316 } 317 318 /** 319 * This method takes an action on the message delivery - dismisses it with the action/cause that comes from the 320 * UI layer 321 * @param action the action or cause of the dismissal 322 * @param message the message to display to the user 323 * @param request the HttpServletRequest 324 * @param response the HttpServletResponse 325 * @return an appropriate ModelAndView 326 */ 327 private ModelAndView dismissMessage(String action, String message, HttpServletRequest request, HttpServletResponse response) { 328 String view = "NotificationDetail"; 329 330 String principalNm = request.getRemoteUser(); 331 String messageDeliveryId = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.MSG_DELIVERY_ID); 332 String command = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.COMMAND); 333 String standaloneWindow = request.getParameter(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW); 334 335 if (messageDeliveryId == null) { 336 throw new RuntimeException("A null messageDeliveryId was provided."); 337 } 338 339 LOG.debug("messageDeliveryId: "+messageDeliveryId); 340 LOG.debug("command: "+command); 341 342 /** 343 * We can get the notification object given a workflow ID or a notification 344 * Id. This method might be called either from a workflow action list or 345 * as a link from a message deliverer endpoint such as an email message. 346 */ 347 NotificationMessageDelivery delivery = messageDeliveryService.getNotificationMessageDelivery(Long.decode(messageDeliveryId)); 348 if (delivery == null) { 349 throw new RuntimeException("Could not find message delivery with id " + messageDeliveryId); 350 } 351 NotificationBo notification = delivery.getNotification(); 352 353 /* 354 * dismiss the message delivery 355 */ 356 357 Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalNm); 358 notificationService.dismissNotificationMessageDelivery(delivery.getId(), principal.getPrincipalId(), action); 359 360 List<NotificationSenderBo> senders = notification.getSenders(); 361 List<NotificationRecipientBo> recipients = notification.getRecipients(); 362 363 String contenthtml = Util.transformContent(notification); 364 365 // first check to see if this is a standalone window, b/c if it is, we'll want to close 366 if(standaloneWindow != null && standaloneWindow.equals("true")) { 367 view = "NotificationActionTakenCloseWindow"; 368 } else { // otherwise check to see if the details need to be rendered in line (no stuff around them) 369 if (command != null && command.equals(NotificationConstants.NOTIFICATION_DETAIL_VIEWS.INLINE)) { 370 view = "NotificationDetailInline"; 371 } 372 } 373 374 Map<String, Object> model = new HashMap<String, Object>(); 375 model.put("notification", notification); 376 model.put("message", message); 377 model.put("senders", senders); 378 model.put("recipients", recipients); 379 model.put("contenthtml", contenthtml); 380 model.put("messageDeliveryId", messageDeliveryId); 381 model.put("command", command); 382 model.put(NotificationConstants.NOTIFICATION_CONTROLLER_CONSTANTS.STANDALONE_WINDOW, standaloneWindow); 383 return new ModelAndView(view, model); 384 } 385}