View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.ken.web.spring;
17  
18  import java.io.IOException;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import javax.servlet.ServletException;
24  import javax.servlet.http.HttpServletRequest;
25  import javax.servlet.http.HttpServletResponse;
26  
27  import org.apache.commons.lang.StringUtils;
28  import org.apache.log4j.Logger;
29  import org.kuali.rice.ken.api.KenApiConstants;
30  import org.kuali.rice.ken.bo.NotificationBo;
31  import org.kuali.rice.ken.bo.NotificationMessageDelivery;
32  import org.kuali.rice.ken.bo.NotificationRecipientBo;
33  import org.kuali.rice.ken.bo.NotificationSenderBo;
34  import org.kuali.rice.ken.service.NotificationMessageDeliveryService;
35  import org.kuali.rice.ken.service.NotificationService;
36  import org.kuali.rice.ken.service.NotificationWorkflowDocumentService;
37  import org.kuali.rice.ken.util.NotificationConstants;
38  import org.kuali.rice.ken.util.Util;
39  import org.kuali.rice.kew.api.KewApiConstants;
40  import org.kuali.rice.kew.api.KewApiServiceLocator;
41  import org.kuali.rice.kim.api.identity.principal.Principal;
42  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
43  import org.kuali.rice.krad.UserSession;
44  import org.kuali.rice.krad.util.KRADUtils;
45  import org.springframework.web.servlet.ModelAndView;
46  import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
47  
48  
49  /**
50   * This class is the controller for the basic notification related actions - viewing, etc.
51   * @author Kuali Rice Team (rice.collab@kuali.org)
52   */
53  public class NotificationController extends MultiActionController {
54      /** Logger for this class and subclasses */
55      private static final Logger LOG = Logger.getLogger(NotificationController.class);
56      
57      protected NotificationService notificationService;
58      protected NotificationWorkflowDocumentService notificationWorkflowDocService;
59      protected NotificationMessageDeliveryService messageDeliveryService;
60     
61      /**
62       * Set the NotificationService
63       * @param notificationService
64       */   
65      public void setNotificationService(NotificationService notificationService) {
66          this.notificationService = notificationService;
67      }
68  
69      /**
70       * This method sets the NotificationWorkflowDocumentService
71       * @param s
72       */
73      public void setNotificationWorkflowDocumentService(NotificationWorkflowDocumentService s) {
74          this.notificationWorkflowDocService = s;
75      }
76  
77      /**
78       * Sets the messageDeliveryService attribute value.
79       * @param messageDeliveryService The messageDeliveryService to set.
80       */
81      public void setMessageDeliveryService(NotificationMessageDeliveryService messageDeliveryService) {
82          this.messageDeliveryService = messageDeliveryService;
83      }
84  
85      /**
86       * Handles the display of the main home page in the system.
87       * @param request : a servlet request
88       * @param response : a servlet response
89       * @throws ServletException : an exception
90       * @throws IOException : an exception
91       * @return a ModelAndView object
92       */   
93      public ModelAndView displayHome(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
94          String view = "HomePage";
95          LOG.debug("remoteUser: "+request.getRemoteUser());
96          Map<String, Object> model = new HashMap<String, Object>(); 
97          return new ModelAndView(view, model);
98      }
99     
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 }