001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/event/tags/sakai_2-4-1/event-util/util/src/java/org/sakaiproject/util/EmailNotification.java $
003: * $Id: EmailNotification.java 13804 2006-08-17 02:47:37Z ggolden@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.util;
021:
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Vector;
025:
026: import org.sakaiproject.component.cover.ServerConfigurationService;
027: import org.sakaiproject.email.cover.DigestService;
028: import org.sakaiproject.email.cover.EmailService;
029: import org.sakaiproject.entity.api.Entity;
030: import org.sakaiproject.entity.api.EntityPropertyNotDefinedException;
031: import org.sakaiproject.entity.api.Reference;
032: import org.sakaiproject.entity.api.ResourceProperties;
033: import org.sakaiproject.entity.cover.EntityManager;
034: import org.sakaiproject.event.api.Event;
035: import org.sakaiproject.event.api.Notification;
036: import org.sakaiproject.event.api.NotificationAction;
037: import org.sakaiproject.event.cover.NotificationService;
038: import org.sakaiproject.site.api.Site;
039: import org.sakaiproject.site.cover.SiteService;
040: import org.sakaiproject.time.cover.TimeService;
041: import org.sakaiproject.tool.cover.SessionManager;
042: import org.sakaiproject.user.api.Preferences;
043: import org.sakaiproject.user.api.User;
044: import org.sakaiproject.user.api.UserNotDefinedException;
045: import org.sakaiproject.user.cover.PreferencesService;
046: import org.sakaiproject.user.cover.UserDirectoryService;
047: import org.w3c.dom.Element;
048:
049: /**
050: * <p>
051: * EmailNotification is the notification action that handles the act of message (email) based notify, site related, with user preferences.
052: * </p>
053: * <p>
054: * The following should be specified to extend the class:
055: * <ul>
056: * <li>getRecipients() - get a collection of Users to send the notification to</li>
057: * <li>getHeaders() - form the complete message headers (like from: to: reply-to: date: subject: etc). from: and to: are for display only</li>
058: * <li>getMessage() - form the complete message body (minus headers)</li>
059: * <li>getTag() - the part of the body at the end that identifies the list</li>
060: * <li>isBodyHTML() - say if your body is html or not (not would be plain text)</li>
061: * </ul>
062: * </p>
063: * <p>
064: * getClone() should also be extended to clone the proper type of object.
065: * </p>
066: */
067: public class EmailNotification implements NotificationAction {
068: /** The related site id. */
069: protected String m_siteId = null;
070:
071: /**
072: * Construct.
073: */
074: public EmailNotification() {
075: }
076:
077: /**
078: * Construct.
079: *
080: * @param siteId
081: * The related site id.
082: */
083: public EmailNotification(String siteId) {
084: m_siteId = siteId;
085: }
086:
087: /**
088: * Set from an xml element.
089: *
090: * @param el
091: * The xml element.
092: */
093: public void set(Element el) {
094: m_siteId = StringUtil.trimToNull(el.getAttribute("site"));
095: }
096:
097: /**
098: * Set from another.
099: *
100: * @param other
101: * The other to copy.
102: */
103: public void set(NotificationAction other) {
104: EmailNotification eOther = (EmailNotification) other;
105: m_siteId = eOther.m_siteId;
106: }
107:
108: /**
109: * Make a new one like me.
110: *
111: * @return A new action just like me.
112: */
113: public NotificationAction getClone() {
114: EmailNotification clone = new EmailNotification();
115: clone.set(this );
116:
117: return clone;
118: }
119:
120: /**
121: * Fill this xml element with the attributes.
122: *
123: * @param el
124: * The xml element.
125: */
126: public void toXml(Element el) {
127: if (m_siteId != null)
128: el.setAttribute("site", m_siteId);
129: }
130:
131: /**
132: * Do the notification.
133: *
134: * @param notification
135: * The notification responding to the event.
136: * @param event
137: * The event that matched criteria to cause the notification.
138: */
139: public void notify(Notification notification, Event event) {
140: // ignore events marked for no notification
141: if (event.getPriority() == NotificationService.NOTI_NONE)
142: return;
143:
144: // get the list of potential recipients
145: List recipients = getRecipients(event);
146:
147: // filter to actual immediate recipients
148: List immediate = immediateRecipients(recipients, notification,
149: event);
150:
151: // and the list of digest recipients
152: List digest = digestRecipients(recipients, notification, event);
153:
154: // we may be done
155: if ((immediate.size() == 0) && (digest.size() == 0))
156: return;
157:
158: // get the email elements - headers (including to: from: subject: date: and anything else we want in the message) and body
159: List headers = getHeaders(event);
160: String message = getMessage(event);
161:
162: // from = "\"" + ServerConfigurationService.getString("ui.service", "Sakai") + "\"<no-reply@" + ServerConfigurationService.getServerName() + ">";
163:
164: // some info we will need to add the tag for immediate recipients
165: boolean isBodyHTML = isBodyHTML(event);
166: String newline = (isBodyHTML) ? "<br />\n" : "\n";
167:
168: // for the immediates
169: if (immediate.size() > 0) {
170: // get a site title: use either the configured site, or if not configured, the site (context) of the resource
171: Reference ref = EntityManager.newReference(event
172: .getResource());
173: Entity r = ref.getEntity();
174: String title = (getSite() != null) ? getSite() : ref
175: .getContext();
176: try {
177: Site site = SiteService.getSite(title);
178: title = site.getTitle();
179: } catch (Exception ignore) {
180: }
181:
182: // add the tag to the end message
183: String messageForImmediates = message
184: + getTag(newline, title);
185:
186: // send it: from: from, to: immediates, body: messageForImmediates, headers: headers
187: EmailService.sendToUsers(immediate, headers,
188: messageForImmediates);
189: }
190:
191: // for the digesters
192: if (digest.size() > 0) {
193: // TODO: \n or newLine? text or htlm? -ggolden
194:
195: // modify the message to add header lines (we don't add a tag for each message, the digest adds a single one when sent)
196: StringBuffer messageForDigest = new StringBuffer();
197:
198: String item = findHeader("From", headers);
199: if (item != null)
200: messageForDigest.append(item);
201:
202: item = findHeader("Date", headers);
203: if (item != null) {
204: messageForDigest.append(item);
205: } else {
206: messageForDigest.append("Date: "
207: + TimeService.newTime().toStringLocalFullZ()
208: + "\n");
209: }
210:
211: item = findHeader("To", headers);
212: if (item != null)
213: messageForDigest.append(item);
214:
215: item = findHeader("Cc", headers);
216: if (item != null)
217: messageForDigest.append(item);
218:
219: item = findHeader("Subject", headers);
220: if (item != null)
221: messageForDigest.append(item);
222:
223: // and the body
224: messageForDigest.append("\n");
225: messageForDigest.append(message);
226:
227: // digest the message to each user
228: for (Iterator iDigests = digest.iterator(); iDigests
229: .hasNext();) {
230: User user = (User) iDigests.next();
231: DigestService.digest(user.getId(), findHeaderValue(
232: "Subject", headers), messageForDigest
233: .toString());
234: }
235: }
236: }
237:
238: /**
239: * Get the message for the email.
240: *
241: * @param event
242: * The event that matched criteria to cause the notification.
243: * @return the message for the email.
244: */
245: protected String getMessage(Event event) {
246: return "";
247: }
248:
249: /**
250: * Get the message tag, the text to display at the bottom of the message.
251: *
252: * @param newline
253: * The newline character(s).
254: * @param title
255: * The title string.
256: * @return The message tag.
257: */
258: protected String getTag(String newline, String title) {
259: return "";
260: }
261:
262: /**
263: * Get headers for the email (List of String, full header lines) - including Subject: Date: To: From: if appropriate, as well as any others
264: *
265: * @param event
266: * The event that matched criteria to cause the notification.
267: * @return the additional headers for the email.
268: */
269: protected List getHeaders(Event event) {
270: return new Vector();
271: }
272:
273: /**
274: * Return true if the body of the email message should be sent as HTML. If this returns true, getHeaders() should also return a "Content-Type: text/html" header of some kind.
275: *
276: * @param event
277: * The event that matched criteria to cause the notification.
278: * @return whether the body of the email message should be sent as HTML.
279: */
280: protected boolean isBodyHTML(Event event) {
281: return false;
282: }
283:
284: /**
285: * Get the list of User objects who are eligible to receive the notification email.
286: *
287: * @param event
288: * The event that matched criteria to cause the notification.
289: * @return the list of User objects who are eligible to receive the notification email.
290: */
291: protected List getRecipients(Event event) {
292: return new Vector();
293: }
294:
295: /**
296: * Get the site id this notification is related to.
297: *
298: * @return The site id this notification is related to.
299: */
300: protected String getSite() {
301: return m_siteId;
302: }
303:
304: /**
305: * Filter the recipients Users into the list of those who get this one immediately. Combine the event's notification priority with the user's notification profile.
306: *
307: * @param recipients
308: * The List (User) of potential recipients.
309: * @param notification
310: * The notification responding to the event.
311: * @param event
312: * The event that matched criteria to cause the notification.
313: * @return The List (User) of immediate recipients.
314: */
315: protected List immediateRecipients(List recipients,
316: Notification notification, Event event) {
317: int priority = event.getPriority();
318:
319: // required notification is sent to all
320: if (priority == NotificationService.NOTI_REQUIRED) {
321: return recipients;
322: }
323:
324: List rv = new Vector();
325: for (Iterator iUsers = recipients.iterator(); iUsers.hasNext();) {
326: User user = (User) iUsers.next();
327:
328: // get the user's priority preference for this event
329: int option = getOption(user, notification, event);
330:
331: // if immediate is the option, or there is no option, select this user
332: // Note: required and none priority are already handled, so we know it's optional here.
333: if (isImmediateDeliveryOption(option, notification)) {
334: rv.add(user);
335: }
336: }
337:
338: return rv;
339: }
340:
341: /**
342: * Filter the preference option based on the notification resource type
343: *
344: * @param option
345: * The preference option.
346: * @param notification
347: * The notification responding to the event.
348: * @return A boolean value which tells if the User is one of immediate recipients.
349: */
350: protected boolean isImmediateDeliveryOption(int option,
351: Notification notification) {
352: if (option == NotificationService.PREF_IMMEDIATE) {
353: return true;
354: } else {
355: if (option == NotificationService.PREF_NONE) {
356: String type = EntityManager.newReference(
357: notification.getResourceFilter()).getType();
358: if (type != null) {
359: if (type
360: .equals("org.sakaiproject.mailarchive.api.MailArchiveService")) {
361: return true;
362: }
363: }
364: }
365: }
366: return false;
367: }
368:
369: /**
370: * Filter the recipients Users into the list of those who get this one by digest. Combine the event's notification priority with the user's notification profile.
371: *
372: * @param recipients
373: * The List (User) of potential recipients.
374: * @param notification
375: * The notification responding to the event.
376: * @param event
377: * The event that matched criteria to cause the notification.
378: * @return The List (User) of digest recipients.
379: */
380: protected List digestRecipients(List recipients,
381: Notification notification, Event event) {
382: List rv = new Vector();
383:
384: int priority = event.getPriority();
385:
386: // priority notification is sent to all (i.e. no digests)
387: if (priority == NotificationService.NOTI_REQUIRED) {
388: return rv;
389: }
390:
391: for (Iterator iUsers = recipients.iterator(); iUsers.hasNext();) {
392: User user = (User) iUsers.next();
393:
394: // get the user's priority preference for this event
395: int option = getOption(user, notification, event);
396:
397: // if digest is the option, select this user
398: if (option == NotificationService.PREF_DIGEST) {
399: rv.add(user);
400: }
401: }
402:
403: return rv;
404: }
405:
406: /**
407: * Get the user's notification option for this... one of the NotificationService's PREF_ settings
408: */
409: protected int getOption(User user, Notification notification,
410: Event event) {
411: String priStr = Integer.toString(event.getPriority());
412:
413: Preferences prefs = PreferencesService.getPreferences(user
414: .getId());
415:
416: // get the user's preference for this notification
417: ResourceProperties props = prefs
418: .getProperties(NotificationService.PREFS_NOTI
419: + notification.getId());
420: try {
421: int option = (int) props.getLongProperty(priStr);
422: if (option != NotificationService.PREF_NONE)
423: return option;
424: } catch (Throwable ignore) {
425: }
426:
427: // try the preference for the site from which resources are being watched for this notification
428: // Note: the getSite() is who is notified, not what we are watching; that's based on the notification filter -ggolden
429: String siteId = EntityManager.newReference(
430: notification.getResourceFilter()).getContext();
431: if (siteId != null) {
432: props = prefs.getProperties(NotificationService.PREFS_SITE
433: + siteId);
434: try {
435: int option = (int) props.getLongProperty(priStr);
436: if (option != NotificationService.PREF_NONE)
437: return option;
438: } catch (Throwable ignore) {
439: }
440: }
441:
442: // try the default
443: props = prefs.getProperties(NotificationService.PREFS_DEFAULT);
444: try {
445: int option = (int) props.getLongProperty(priStr);
446: if (option != NotificationService.PREF_NONE)
447: return option;
448: } catch (Throwable ignore) {
449: }
450:
451: // try the preference for the resource type service responsibile for resources of this notification
452: String type = EntityManager.newReference(
453: notification.getResourceFilter()).getType();
454: if (type != null) {
455: props = prefs.getProperties(NotificationService.PREFS_TYPE
456: + type);
457: try {
458: int option = (int) props.getLongProperty(Integer
459: .toString(NotificationService.NOTI_OPTIONAL));
460: if (option != NotificationService.PREF_NONE)
461: return option;
462: } catch (EntityPropertyNotDefinedException e) {
463: return NotificationService.PREF_IMMEDIATE;
464: } catch (Throwable ignore) {
465: }
466: }
467:
468: // nothing defined...
469: return NotificationService.PREF_NONE;
470: }
471:
472: /**
473: * Find the header line that begins with the header parameter
474: *
475: * @param header
476: * The header to find.
477: * @param headers
478: * The list of full header lines.
479: * @return The header line found or null if not found.
480: */
481: protected String findHeader(String header, List headers) {
482: for (Iterator i = headers.iterator(); i.hasNext();) {
483: String h = (String) i.next();
484: if (h.startsWith(header))
485: return h;
486: }
487:
488: return null;
489: }
490:
491: /**
492: * Find the header value whose name matches with the header parameter
493: *
494: * @param header
495: * The header to find.
496: * @param headers
497: * The list of full header lines.
498: * @return The header line found or null if not found.
499: */
500: protected String findHeaderValue(String header, List headers) {
501: String line = findHeader(header, headers);
502: if (line == null)
503: return null;
504:
505: String value = line.substring(header.length() + 2);
506: return value;
507: }
508:
509: /**
510: * Format a From: respecting the notification service replyable configuration
511: *
512: * @param event
513: * @return
514: */
515: protected String getFrom(Event event) {
516: if (NotificationService.isNotificationFromReplyable()) {
517: // from user display name <email>
518: return "From: " + getFromEventUser(event);
519: } else {
520: // from the general service, no reply
521: return "From: " + getFromService();
522: }
523: }
524:
525: /**
526: * Format a from address from the service, no reply.
527: *
528: * @return a from address from the service, no reply.
529: */
530: protected String getFromService() {
531: return "\""
532: + ServerConfigurationService.getString("ui.service",
533: "Sakai") + "\"<no-reply@"
534: + ServerConfigurationService.getServerName() + ">";
535: }
536:
537: /**
538: * Format the from user email address based on the user generating the event (current user).
539: *
540: * @param event
541: * The event that matched criteria to cause the notification.
542: * @return the from user email address based on the user generating the event.
543: */
544: protected String getFromEventUser(Event event) {
545: String userDisplay = null;
546: String userEmail = null;
547:
548: String userId = SessionManager.getCurrentSessionUserId();
549: if (userId != null) {
550: try {
551: User u = UserDirectoryService.getUser(userId);
552: userDisplay = u.getDisplayName();
553: userEmail = u.getEmail();
554: if ((userEmail != null)
555: && (userEmail.trim().length()) == 0)
556: userEmail = null;
557: } catch (UserNotDefinedException e) {
558: }
559: }
560:
561: // some fallback positions
562: if (userEmail == null)
563: userEmail = "no-reply@"
564: + ServerConfigurationService.getServerName();
565: if (userDisplay == null)
566: userDisplay = ServerConfigurationService.getString(
567: "ui.service", "Sakai");
568:
569: return "\"" + userDisplay + "\" <" + userEmail + ">";
570: }
571: }
|