View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.ken.web.spring;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
21  import org.kuali.rice.core.framework.persistence.dao.GenericDao;
22  import org.kuali.rice.ken.bo.NotificationBo;
23  import org.kuali.rice.ken.bo.NotificationChannelBo;
24  import org.kuali.rice.ken.bo.NotificationChannelReviewerBo;
25  import org.kuali.rice.ken.bo.NotificationContentTypeBo;
26  import org.kuali.rice.ken.bo.NotificationPriorityBo;
27  import org.kuali.rice.ken.bo.NotificationProducerBo;
28  import org.kuali.rice.ken.bo.NotificationRecipientBo;
29  import org.kuali.rice.ken.bo.NotificationSenderBo;
30  import org.kuali.rice.ken.document.kew.NotificationWorkflowDocument;
31  import org.kuali.rice.ken.exception.ErrorList;
32  import org.kuali.rice.ken.service.NotificationChannelService;
33  import org.kuali.rice.ken.service.NotificationMessageContentService;
34  import org.kuali.rice.ken.service.NotificationRecipientService;
35  import org.kuali.rice.ken.service.NotificationService;
36  import org.kuali.rice.ken.service.NotificationWorkflowDocumentService;
37  import org.kuali.rice.ken.util.NotificationConstants;
38  import org.kuali.rice.ken.util.Util;
39  import org.kuali.rice.kew.api.WorkflowDocument;
40  import org.kuali.rice.kew.rule.GenericAttributeContent;
41  import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes;
42  import org.kuali.rice.kim.api.identity.principal.Principal;
43  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
44  import org.springframework.web.servlet.ModelAndView;
45  
46  import javax.servlet.ServletException;
47  import javax.servlet.http.HttpServletRequest;
48  import javax.servlet.http.HttpServletResponse;
49  import java.io.IOException;
50  import java.sql.Timestamp;
51  import java.text.ParseException;
52  import java.util.Date;
53  import java.util.HashMap;
54  import java.util.List;
55  import java.util.Map;
56  
57  
58  /**
59   * This class is the controller for sending Event notification messages via an end user interface.
60   * @author Kuali Rice Team (rice.collab@kuali.org)
61   */
62  public class SendEventNotificationMessageController extends BaseSendNotificationController {
63      /** Logger for this class and subclasses */
64      private static final Logger LOG = Logger
65  	    .getLogger(SendEventNotificationMessageController.class);
66  
67      private static final String NONE_CHANNEL = "___NONE___";
68      private static final long REASONABLE_IMMEDIATE_TIME_THRESHOLD = 1000 * 60 * 5; // <= 5 minutes is "immediate"
69  
70      /**
71       * Returns whether the specified time is considered "in the future", based on some reasonable threshold
72       * @param time the time to test
73       * @return whether the specified time is considered "in the future", based on some reasonable threshold
74       */
75      private boolean timeIsInTheFuture(long time) {
76          boolean future = (time - System.currentTimeMillis()) > REASONABLE_IMMEDIATE_TIME_THRESHOLD;
77          LOG.info("Time: " + new Date(time) + " is in the future? " + future);
78          return future;
79      }
80  
81      /**
82       * Returns whether the specified Notification can be reasonably expected to have recipients.
83       * This is determined on whether the channel has default recipients, is subscribably, and whether
84       * the send date time is far enough in the future to expect that if there are no subscribers, there
85       * may actually be some by the time the notification is sent.
86       * @param notification the notification to test
87       * @return whether the specified Notification can be reasonably expected to have recipients
88       */
89      private boolean hasPotentialRecipients(NotificationBo notification) {
90          LOG.info("notification channel " + notification.getChannel() + " is subscribable: " + notification.getChannel().isSubscribable());
91          return notification.getChannel().getRecipientLists().size() > 0 ||
92                 notification.getChannel().getSubscriptions().size() > 0 ||
93                 (notification.getChannel().isSubscribable() && timeIsInTheFuture(notification.getSendDateTimeValue().getTime()));
94      }
95  
96      protected NotificationService notificationService;
97  
98      protected NotificationWorkflowDocumentService notificationWorkflowDocService;
99  
100     protected NotificationChannelService notificationChannelService;
101 
102     protected NotificationRecipientService notificationRecipientService;
103 
104     protected NotificationMessageContentService messageContentService;
105 
106     protected GenericDao businessObjectDao;
107 
108     /**
109      * Set the NotificationService
110      * @param notificationService
111      */
112     public void setNotificationService(NotificationService notificationService) {
113 	this.notificationService = notificationService;
114     }
115 
116     /**
117      * This method sets the NotificationWorkflowDocumentService
118      * @param s
119      */
120     public void setNotificationWorkflowDocumentService(
121 	    NotificationWorkflowDocumentService s) {
122 	this.notificationWorkflowDocService = s;
123     }
124 
125     /**
126      * Sets the notificationChannelService attribute value.
127      * @param notificationChannelService The notificationChannelService to set.
128      */
129     public void setNotificationChannelService(
130 	    NotificationChannelService notificationChannelService) {
131 	this.notificationChannelService = notificationChannelService;
132     }
133 
134     /**
135      * Sets the notificationRecipientService attribute value.
136      * @param notificationRecipientService
137      */
138     public void setNotificationRecipientService(
139 	    NotificationRecipientService notificationRecipientService) {
140 	this.notificationRecipientService = notificationRecipientService;
141     }
142 
143     /**
144      * Sets the messageContentService attribute value.
145      * @param messageContentService
146      */
147     public void setMessageContentService(
148 	    NotificationMessageContentService notificationMessageContentService) {
149 	this.messageContentService = notificationMessageContentService;
150     }
151 
152     /**
153      * Sets the businessObjectDao attribute value.
154      * @param businessObjectDao The businessObjectDao to set.
155      */
156     public void setBusinessObjectDao(GenericDao businessObjectDao) {
157 	this.businessObjectDao = businessObjectDao;
158     }
159 
160     /**
161      * Handles the display of the form for sending an event notification message
162      * @param request : a servlet request
163      * @param response : a servlet response
164      * @throws ServletException : an exception
165      * @throws IOException : an exception
166      * @return a ModelAndView object
167      */
168     public ModelAndView sendEventNotificationMessage(
169 	    HttpServletRequest request, HttpServletResponse response)
170 	    throws ServletException, IOException {
171 	String view = "SendEventNotificationMessage";
172 	LOG.debug("remoteUser: " + request.getRemoteUser());
173 
174 	Map<String, Object> model = setupModelForSendEventNotification(request);
175 	model.put("errors", new ErrorList()); // need an empty one so we don't have an NPE
176 
177 	return new ModelAndView(view, model);
178     }
179 
180     /**
181      * This method prepares the model used for the send event notification message form.
182      * @param request
183      * @return Map<String, Object>
184      */
185     private Map<String, Object> setupModelForSendEventNotification(
186 	    HttpServletRequest request) {
187 	Map<String, Object> model = new HashMap<String, Object>();
188 	model.put("defaultSender", request.getRemoteUser());
189 	model.put("channels", notificationChannelService
190 		.getAllNotificationChannels());
191 	model.put("priorities", businessObjectDao
192 		.findAll(NotificationPriorityBo.class));
193         // set sendDateTime to current datetime if not provided
194 	String sendDateTime = request.getParameter("sendDateTime");
195 	String currentDateTime = Util.getCurrentDateTime();
196 	if (StringUtils.isEmpty(sendDateTime)) {
197 	    sendDateTime = currentDateTime;
198 	}
199 	model.put("sendDateTime", sendDateTime);
200 
201 	// retain the original date time or set to current if
202 	// it was not in the request
203 	if (request.getParameter("originalDateTime") == null) {
204 	   model.put("originalDateTime", currentDateTime);
205 	} else {
206 	   model.put("originalDateTime", request.getParameter("originalDateTime"));
207 	}
208         model.put("summary", request.getParameter("summary"));
209         model.put("description", request.getParameter("description"));
210         model.put("location", request.getParameter("location"));
211         model.put("startDateTime", request.getParameter("startDateTime"));
212         model.put("stopDateTime", request.getParameter("stopDateTime"));
213 
214         model.put("userRecipients", request.getParameter("userRecipients"));
215         model.put("workgroupRecipients", request.getParameter("workgroupRecipients"));
216         model.put("workgroupNamespaceCodes", request.getParameter("workgroupNamespaceCodes"));
217 
218 	return model;
219     }
220 
221     /**
222      * This method handles submitting the actual event notification message.
223      * @param request
224      * @param response
225      * @return ModelAndView
226      * @throws ServletException
227      * @throws IOException
228      */
229     public ModelAndView submitEventNotificationMessage(
230 	    HttpServletRequest request, HttpServletResponse response)
231 	    throws ServletException, IOException {
232 	LOG.debug("remoteUser: " + request.getRemoteUser());
233 
234 	// obtain a workflow user object first
235 	//WorkflowIdDTO initiator = new WorkflowIdDTO(request.getRemoteUser());
236     String initiatorId = getPrincipalIdFromIdOrName( request.getRemoteUser());
237     LOG.debug("initiatorId="+initiatorId);
238 
239 	// now construct the workflow document, which will interact with workflow
240 	WorkflowDocument document;
241 	Map<String, Object> model = new HashMap<String, Object>();
242         String view;
243 	try {
244 	    document = NotificationWorkflowDocument.createNotificationDocument(
245 	    		initiatorId,
246 			    NotificationConstants.KEW_CONSTANTS.SEND_NOTIFICATION_REQ_DOC_TYPE);
247 
248 	    //parse out the application content into a Notification BO
249 	    NotificationBo notification = populateNotificationInstance(request, model);
250 
251 	    // now get that content in an understandable XML format and pass into document
252 	    String notificationAsXml = messageContentService
253 		    .generateNotificationMessage(notification);
254 
255             Map<String, String> attrFields = new HashMap<String,String>();
256             List<NotificationChannelReviewerBo> reviewers = notification.getChannel().getReviewers();
257             int ui = 0;
258             int gi = 0;
259             for (NotificationChannelReviewerBo reviewer: reviewers) {
260                 String prefix;
261                 int index;
262                 if (KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.equals(reviewer.getReviewerType())) {
263                     prefix = "user";
264                     index = ui;
265                     ui++;
266                 } else if (KimGroupMemberTypes.GROUP_MEMBER_TYPE.equals(reviewer.getReviewerType())) {
267                     prefix = "group";
268                     index = gi;
269                     gi++;
270                 } else {
271                     LOG.error("Invalid type for reviewer " + reviewer.getReviewerId() + ": " + reviewer.getReviewerType());
272                     continue;
273                 }
274                 attrFields.put(prefix + index, reviewer.getReviewerId());
275             }
276             GenericAttributeContent gac = new GenericAttributeContent("channelReviewers");
277             document.setApplicationContent(notificationAsXml);
278             document.setAttributeContent("<attributeContent>" + gac.generateContent(attrFields) + "</attributeContent>");
279 
280             document.setTitle(notification.getTitle());
281 
282 	    document.route("This message was submitted via the event notification message submission form by user "
283 			    + initiatorId);
284 
285 	    view = "SendEventNotificationMessage";
286 	    
287 	    // This ain't pretty, but it gets the job done for now.
288 	    ErrorList el = new ErrorList();
289 	    el.addError("Notification(s) sent.");
290 	    model.put("errors", el);
291 	    
292 	} catch (ErrorList el) {
293 	    // route back to the send form again
294 	    Map<String, Object> model2 = setupModelForSendEventNotification(request);
295 	    model.putAll(model2);
296 	    model.put("errors", el);
297 
298 	    view = "SendEventNotificationMessage";
299 	} catch (Exception e) {
300 	    throw new RuntimeException(e);
301 	}
302 
303 	return new ModelAndView(view, model);
304     }
305 
306     /**
307      * This method creates a new Notification instance from the event form values.
308      * @param request
309      * @param model
310      * @return Notification
311      * @throws IllegalArgumentException
312      */
313     private NotificationBo populateNotificationInstance(
314 	    HttpServletRequest request, Map<String, Object> model)
315 	    throws IllegalArgumentException, ErrorList {
316 	ErrorList errors = new ErrorList();
317 
318 	NotificationBo notification = new NotificationBo();
319 
320 	// grab data from form
321 	// channel name
322 	String channelName = request.getParameter("channelName");
323         if (StringUtils.isEmpty(channelName) || StringUtils.equals(channelName, NONE_CHANNEL)) {
324 	    errors.addError("You must choose a channel.");
325 	} else {
326 	    model.put("channelName", channelName);
327 	}
328 
329 	// priority name
330 	String priorityName = request.getParameter("priorityName");
331 	if (StringUtils.isEmpty(priorityName)) {
332 	    errors.addError("You must choose a priority.");
333 	} else {
334 	    model.put("priorityName", priorityName);
335 	}
336 
337 	// sender names
338 	String senderNames = request.getParameter("senderNames");
339 	String[] senders = null;
340 	if (StringUtils.isEmpty(senderNames)) {
341 	    errors.addError("You must enter at least one sender.");
342 	} else {
343 	    senders = StringUtils.split(senderNames, ",");
344 
345 	    model.put("senderNames", senderNames);
346 	}
347 
348 	// delivery type
349 	String deliveryType = request.getParameter("deliveryType");
350 	if (StringUtils.isEmpty(deliveryType)) {
351 	    errors.addError("You must choose a type.");
352 	} else {
353 	    if (deliveryType
354 		    .equalsIgnoreCase(NotificationConstants.DELIVERY_TYPES.FYI)) {
355 		deliveryType = NotificationConstants.DELIVERY_TYPES.FYI;
356 	    } else {
357 		deliveryType = NotificationConstants.DELIVERY_TYPES.ACK;
358 	    }
359 	    model.put("deliveryType", deliveryType);
360 	}
361 
362 	//get datetime when form was initially rendered
363 	String originalDateTime = request.getParameter("originalDateTime");
364 	Date origdate = null;
365 	Date senddate = null;
366 	Date removedate = null;
367 	try {
368             origdate = Util.parseUIDateTime(originalDateTime);
369         } catch (ParseException pe) {
370             errors.addError("Original date is invalid.");
371         }
372 	// send date time
373 	String sendDateTime = request.getParameter("sendDateTime");
374 	if (StringUtils.isBlank(sendDateTime)) {
375 	    sendDateTime = Util.getCurrentDateTime();
376 	}
377 
378 	try {
379             senddate = Util.parseUIDateTime(sendDateTime);
380         } catch (ParseException pe) {
381             errors.addError("You specified an invalid Send Date/Time.  Please use the calendar picker.");
382         }
383 
384         if(senddate != null && senddate.before(origdate)) {
385             errors.addError("Send Date/Time cannot be in the past.");
386         }
387 
388         model.put("sendDateTime", sendDateTime);
389 
390 	// auto remove date time
391 	String autoRemoveDateTime = request.getParameter("autoRemoveDateTime");
392 	if (StringUtils.isNotBlank(autoRemoveDateTime)) {
393 	    try {
394                 removedate = Util.parseUIDateTime(autoRemoveDateTime);
395             } catch (ParseException pe) {
396                 errors.addError("You specified an invalid Auto-Remove Date/Time.  Please use the calendar picker.");
397             }
398 
399             if(removedate != null) {
400         	if(removedate.before(origdate)) {
401         	    errors.addError("Auto-Remove Date/Time cannot be in the past.");
402         	} else if (senddate != null && removedate.before(senddate)){
403         	    errors.addError("Auto-Remove Date/Time cannot be before the Send Date/Time.");
404         	}
405             }
406 	}
407 
408 	model.put("autoRemoveDateTime", autoRemoveDateTime);
409 
410 	// user recipient names
411 	String[] userRecipients = parseUserRecipients(request);
412 
413 	// workgroup recipient names
414 	String[] workgroupRecipients = parseWorkgroupRecipients(request);
415 
416 	// workgroup namespace names
417 	String[] workgroupNamespaceCodes = parseWorkgroupNamespaceCodes(request);
418 	
419 	// title
420         String title = request.getParameter("title");
421         if (!StringUtils.isEmpty(title)) {
422             model.put("title", title);
423         } else {
424             errors.addError("You must fill in a title");
425         }
426 
427 	// message
428 	String message = request.getParameter("message");
429 	if (StringUtils.isEmpty(message)) {
430 	    errors.addError("You must fill in a message.");
431 	} else {
432 	    model.put("message", message);
433 	}
434 
435         // all event fields are mandatory for event type
436 
437 	// start date time
438         String startDateTime = request.getParameter("startDateTime");
439         if (StringUtils.isEmpty(startDateTime)) {
440             errors.addError("You must fill in a start date/time.");
441         } else {
442             model.put("startDateTime", startDateTime);
443         }
444 
445         // stop date time
446         String stopDateTime = request.getParameter("stopDateTime");
447         if (StringUtils.isEmpty(stopDateTime)) {
448             errors.addError("You must fill in a stop date/time.");
449         } else {
450             model.put("stopDateTime", stopDateTime);
451         }
452 
453         // summary
454         String summary = request.getParameter("summary");
455         if (StringUtils.isEmpty(summary)) {
456             errors.addError("You must fill in a summary.");
457         } else {
458             model.put("summary", summary);
459         }
460 
461         // description
462         String description = request.getParameter("description");
463         if (StringUtils.isEmpty(description)) {
464             errors.addError("You must fill in a description.");
465         } else {
466             model.put("description", description);
467         }
468 
469         // location
470         String location = request.getParameter("location");
471         if (StringUtils.isEmpty(location)) {
472             errors.addError("You must fill in a location.");
473         } else {
474             model.put("location", location);
475         }
476 
477 	// stop processing if there are errors
478 	if (errors.getErrors().size() > 0) {
479 	    throw errors;
480 	}
481 
482 	// now populate the notification BO instance
483 	NotificationChannelBo channel = Util.retrieveFieldReference("channel",
484 		"name", channelName, NotificationChannelBo.class,
485 		businessObjectDao);
486 	notification.setChannel(channel);
487 
488 	NotificationPriorityBo priority = Util.retrieveFieldReference("priority",
489 		"name", priorityName, NotificationPriorityBo.class,
490 		businessObjectDao);
491 	notification.setPriority(priority);
492 
493 	NotificationContentTypeBo contentType = Util.retrieveFieldReference(
494 		"contentType", "name",
495 		NotificationConstants.CONTENT_TYPES.EVENT_CONTENT_TYPE,
496 		NotificationContentTypeBo.class, businessObjectDao);
497 	notification.setContentType(contentType);
498 
499 	NotificationProducerBo producer = Util
500 		.retrieveFieldReference(
501 			"producer",
502 			"name",
503 			NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER_NAME,
504 			NotificationProducerBo.class, businessObjectDao);
505 	notification.setProducer(producer);
506 
507 	for (String senderName : senders) {
508 	    if (StringUtils.isEmpty(senderName)) {
509 		errors.addError("A sender's name cannot be blank.");
510 	    } else {
511 		NotificationSenderBo ns = new NotificationSenderBo();
512 		ns.setSenderName(senderName.trim());
513 		notification.addSender(ns);
514 	    }
515 	}
516 
517 	boolean recipientsExist = false;
518 
519 	if (userRecipients != null && userRecipients.length > 0) {
520 	    recipientsExist = true;
521 	    for (String userRecipientId : userRecipients) {
522 	        if (isUserRecipientValid(userRecipientId, errors)) {
523         		NotificationRecipientBo recipient = new NotificationRecipientBo();
524         		recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode());
525         		recipient.setRecipientId(userRecipientId);
526         		notification.addRecipient(recipient);
527 	        }
528 	    }
529 	}
530 
531 	if (workgroupRecipients != null && workgroupRecipients.length > 0) {
532 	    recipientsExist = true;
533 	    if (workgroupNamespaceCodes != null && workgroupNamespaceCodes.length > 0) {
534 	    	if (workgroupNamespaceCodes.length == workgroupRecipients.length) {
535 	    		for (int i = 0; i < workgroupRecipients.length; i++) {
536 	    			if (isWorkgroupRecipientValid(workgroupRecipients[i], workgroupNamespaceCodes[i], errors)) {
537 	    				NotificationRecipientBo recipient = new NotificationRecipientBo();
538 	    				recipient.setRecipientType(KimGroupMemberTypes.GROUP_MEMBER_TYPE.getCode());
539 	    				recipient.setRecipientId(
540 	    						getGroupService().getGroupByNamespaceCodeAndName(workgroupNamespaceCodes[i],
541                                         workgroupRecipients[i]).getId());
542 	    				notification.addRecipient(recipient);
543 	    			}
544 	    		}
545 	    	} else {
546 	    		errors.addError("The number of groups must match the number of namespace codes");
547 	    	}
548 	    } else {
549 			errors.addError("You must specify a namespace code for every group name");
550 		}
551 	} else if (workgroupNamespaceCodes != null && workgroupNamespaceCodes.length > 0) {
552 		errors.addError("You must specify a group name for every namespace code");
553 	}
554 
555 	// check to see if there were any errors
556 	if (errors.getErrors().size() > 0) {
557 	    throw errors;
558 	}
559 
560         notification.setTitle(title);
561 
562 	notification.setDeliveryType(deliveryType);
563 
564         Date startDate = null;
565         Date stopDate = null;
566 	// simpledateformat is not threadsafe, have to sync and validate
567         Date d = null;
568         if (StringUtils.isNotBlank(sendDateTime)) {
569             try {
570                 d = Util.parseUIDateTime(sendDateTime);
571             } catch (ParseException pe) {
572                 errors.addError("You specified an invalid send date and time.  Please use the calendar picker.");
573             }
574             notification.setSendDateTimeValue(new Timestamp(d.getTime()));
575         }
576 
577         Date d2 = null;
578         if (StringUtils.isNotBlank(autoRemoveDateTime)) {
579             try {
580                 d2 = Util.parseUIDateTime(autoRemoveDateTime);
581                 if (d2.before(d)) {
582                     errors.addError("Auto Remove Date/Time cannot be before Send Date/Time.");
583                 }
584             } catch (ParseException pe) {
585                 errors.addError("You specified an invalid auto-remove date and time.  Please use the calendar picker.");
586             }
587             notification.setAutoRemoveDateTimeValue(new Timestamp(d2.getTime()));
588         }
589 
590         if (StringUtils.isNotBlank(startDateTime)) {
591             try {
592                 startDate = Util.parseUIDateTime(startDateTime);
593             } catch (ParseException pe) {
594                 errors.addError("You specified an invalid start date and time.  Please use the calendar picker.");
595             }
596         }
597 
598         if (StringUtils.isNotBlank(stopDateTime)) {
599             try {
600                 stopDate = Util.parseUIDateTime(stopDateTime);
601             } catch (ParseException pe) {
602                 errors.addError("You specified an invalid stop date and time.  Please use the calendar picker.");
603             }
604         }
605 
606         if(stopDate != null && startDate != null) {
607             if (stopDate.before(startDate)) {
608                 errors.addError("Event Stop Date/Time cannot be before Event Start Date/Time.");
609             }
610         }
611 
612         if (!recipientsExist && !hasPotentialRecipients(notification)) {
613             errors.addError("You must specify at least one user or group recipient.");
614         }
615 
616 	// check to see if there were any errors
617 	if (errors.getErrors().size() > 0) {
618 	    throw errors;
619 	}
620 
621 	notification
622 		.setContent(NotificationConstants.XML_MESSAGE_CONSTANTS.CONTENT_EVENT_OPEN
623 			+ NotificationConstants.XML_MESSAGE_CONSTANTS.MESSAGE_OPEN
624 			+ message
625 			+ NotificationConstants.XML_MESSAGE_CONSTANTS.MESSAGE_CLOSE
626                         + "<event>\n"
627                         + "  <summary>" + summary + "</summary>\n"
628                         + "  <description>" + description + "</description>\n"
629                         + "  <location>" + location + "</location>\n"
630                         + "  <startDateTime>" + Util.toUIDateTimeString(startDate) + "</startDateTime>\n"
631                         + "  <stopDateTime>" + Util.toUIDateTimeString(stopDate) + "</stopDateTime>\n"
632                         + "</event>"
633 			+ NotificationConstants.XML_MESSAGE_CONSTANTS.CONTENT_CLOSE);
634 
635 	return notification;
636     }
637 }