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 }