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