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