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 org.apache.commons.lang.StringUtils;
019import org.apache.log4j.Logger;
020import org.kuali.rice.ken.bo.NotificationBo;
021import org.kuali.rice.ken.bo.NotificationChannelReviewerBo;
022import org.kuali.rice.ken.document.kew.NotificationWorkflowDocument;
023import org.kuali.rice.ken.service.NotificationMessageContentService;
024import org.kuali.rice.ken.service.NotificationRecipientService;
025import org.kuali.rice.ken.service.NotificationWorkflowDocumentService;
026import org.kuali.rice.ken.util.NotificationConstants;
027import org.kuali.rice.ken.util.Util;
028import org.kuali.rice.kew.api.WorkflowDocument;
029import org.kuali.rice.kew.api.WorkflowDocumentFactory;
030import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes;
031import org.kuali.rice.kim.api.identity.Person;
032import org.kuali.rice.kim.api.services.KimApiServiceLocator;
033import org.springframework.validation.BindException;
034import org.springframework.validation.ValidationUtils;
035import org.springframework.web.bind.ServletRequestBindingException;
036import org.springframework.web.servlet.ModelAndView;
037import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
038
039import javax.servlet.ServletException;
040import javax.servlet.http.HttpServletRequest;
041import javax.servlet.http.HttpServletResponse;
042import java.util.Date;
043import java.util.HashMap;
044import java.util.List;
045import 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 */
053public 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 NotificationBo 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 NotificationBo getNotification() {
083            return notification;
084        }
085        public void setNotification(NotificationBo 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 NotificationBo retrieveNotificationForWorkflowDocument(WorkflowDocument document) throws Exception {
147        String notificationAsXml = document.getApplicationContent();
148
149        //parse out the application content into a Notification BO
150        NotificationBo 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            NotificationBo 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.getAutoRemoveDateTimeValue().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<NotificationChannelReviewerBo> reviewers = notification.getChannel().getReviewers();
207                    String user = null;
208                    for (NotificationChannelReviewerBo 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            NotificationBo 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}