001/**
002 * Copyright 2005-2016 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}