Index: core-providers/src/java/notify.properties =================================================================== --- core-providers/src/java/notify.properties (revision 0) +++ core-providers/src/java/notify.properties (working copy) @@ -0,0 +1,16 @@ +# Added for https://jira.sakaiproject.org/browse/SAK-25733 +# REST API +notify = Allows access to Sakai's email notification handling +notify.action.post = Creates a new notification. Expects /notify/post/:ID: + +# Subject +notify_post_subject = External Email Notification + +# External Notification User Prefs +prefs_title = External Notifications +# Do not remove prefs_description or prefs_title_override, check SAK-21078 for risk and code for reason +prefs_description= +prefs_title_override = +prefs_opt3 = Send me each notification separately +prefs_opt2 = Send me one email per day summarizing all external notifications +prefs_opt1 = Do not send me external notifications Property changes on: core-providers/src/java/notify.properties ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: core-providers/src/java/org/sakaiproject/entitybroker/providers/ExternalEmailNotification.java =================================================================== --- core-providers/src/java/org/sakaiproject/entitybroker/providers/ExternalEmailNotification.java (revision 0) +++ core-providers/src/java/org/sakaiproject/entitybroker/providers/ExternalEmailNotification.java (working copy) @@ -0,0 +1,185 @@ +/** + * $Id$ + * $URL$ + ************************************************************************** + * Copyright (c) 2014 The Sakai Foundation + * + * Licensed under the Educational Community License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.opensource.org/licenses/ECL-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakaiproject.entitybroker.providers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.sakaiproject.component.cover.ComponentManager; +import org.sakaiproject.event.api.Event; +import org.sakaiproject.user.api.User; +import org.sakaiproject.user.api.UserDirectoryService; +import org.sakaiproject.user.api.UserNotDefinedException; +import org.sakaiproject.util.EmailNotification; +import org.sakaiproject.util.ResourceLoader; + +/** + * ExternalEmailNotification handles events generated by {@link NotificationEntityProvider}. It generates the emails + * requested by the external system. + * + * Added for https://jira.sakaiproject.org/browse/SAK-25733 + * @author Bill Smith (wsmith @ unicon.net) + * @author Aaron Zeckoski (azeckoski @ gmail.com) (azeckoski @ unicon.net) + */ +public class ExternalEmailNotification extends EmailNotification { + private static Log log = LogFactory.getLog(ExternalEmailNotification.class); + + private static final String EMAIL_SUBJECT_PROP = "notify_post_subject"; + + private final String MULTIPART_BOUNDARY = "======sakai-multi-part-boundary======"; + private final String BOUNDARY_LINE = "\n\n--" + MULTIPART_BOUNDARY + "\n"; + private final String MIME_ADVISORY = "This message is for MIME-compliant mail readers."; + private final String TERMINATION_LINE = "\n\n--" + MULTIPART_BOUNDARY + "--\n\n"; + + private ThreadLocal message = new ThreadLocal(); + + private UserDirectoryService userDirectoryService; + + /** + * Default constructor + */ + public ExternalEmailNotification() { + this.userDirectoryService = (UserDirectoryService) ComponentManager.get(UserDirectoryService.class); + } + + /** + * To be called when a new event is being created. Allows the event to set the emailBody content for the + * notification. + * + * @param emailBody + * the email body to be added + */ + public void addMessage(String emailBody) { + if (log.isDebugEnabled()) { + log.debug("Adding new request!\nemailBody: " + emailBody); + } + message.set(emailBody); + } + + /* (non-Javadoc) + * @see org.sakaiproject.util.EmailNotification#getHeaders(org.sakaiproject.event.api.Event) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected List getHeaders(Event event) { + List rv = super.getHeaders(event); + + // set the subject + try { + User user = this.userDirectoryService.getUser(event.getContext()); + // get subject from resource bundle based on user's locale + ResourceBundle bundle = ResourceBundle.getBundle("notify", getUserLocale(user.getId())); + rv.add("Subject: " + bundle.getString(EMAIL_SUBJECT_PROP)); + } catch (UserNotDefinedException e) { + throw new IllegalStateException(e); + } + + // to + rv.add(getTo(event)); + + return rv; + } + + /** + * @param userId Sakai user id + * @return correct Locale for the user based on prefs + */ + private Locale getUserLocale(String userId) { + Locale locale = new ResourceLoader().getLocale(userId); + if (locale == null) { + // since this can be a null if it is not set we will force it to make sure it is good + locale = new ResourceLoader().getLocale(); + if (locale == null) { + locale = Locale.getDefault(); + } + } + return locale; + } + + /* (non-Javadoc) + * @see org.sakaiproject.util.EmailNotification#getMessage(org.sakaiproject.event.api.Event) + */ + @Override + protected String getMessage(Event event) { + StringBuilder message = new StringBuilder(); + message.append(MIME_ADVISORY); + + message.append(BOUNDARY_LINE); + message.append(plainTextHeaders()); + message.append(plainTextContent(event)); + + message.append(TERMINATION_LINE); + return message.toString(); + } + + /* (non-Javadoc) + * @see org.sakaiproject.util.EmailNotification#getRecipients(org.sakaiproject.event.api.Event) + */ + @Override + protected List getRecipients(Event event) { + ArrayList recipients = new ArrayList(); + String userId = event.getContext(); + try { + User user = this.userDirectoryService.getUser(userId); + recipients.add(user); + } catch (UserNotDefinedException e) { + throw new IllegalStateException(e); + } + return recipients; + } + + /** + * Generate the To header based on the event data + * @param event + * @return + */ + private String getTo(Event event) { + String to = null; + try { + User user = this.userDirectoryService.getUser(event.getContext()); + to = "To: " + user.getEmail(); + } catch (UserNotDefinedException e) { + throw new IllegalStateException(e); + } + return to; + } + + /* (non-Javadoc) + * @see org.sakaiproject.util.EmailNotification#plainTextContent(org.sakaiproject.event.api.Event) + */ + @Override + protected String plainTextContent(Event event) { + StringBuilder buf = new StringBuilder(); + buf.append(message.get()); + return buf.toString(); + } + + /* (non-Javadoc) + * @see org.sakaiproject.util.EmailNotification#getType + */ + protected String getType(String resourceFilter) { + return "sakai:external"; + } + +} Property changes on: core-providers/src/java/org/sakaiproject/entitybroker/providers/ExternalEmailNotification.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: core-providers/src/java/org/sakaiproject/entitybroker/providers/NotificationEntityProvider.java =================================================================== --- core-providers/src/java/org/sakaiproject/entitybroker/providers/NotificationEntityProvider.java (revision 0) +++ core-providers/src/java/org/sakaiproject/entitybroker/providers/NotificationEntityProvider.java (working copy) @@ -0,0 +1,197 @@ +/** + * $Id$ + * $URL$ + ************************************************************************** + * Copyright (c) 2014 The Sakai Foundation + * + * Licensed under the Educational Community License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.opensource.org/licenses/ECL-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakaiproject.entitybroker.providers; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.sakaiproject.entitybroker.EntityView; +import org.sakaiproject.entitybroker.entityprovider.annotations.EntityCustomAction; +import org.sakaiproject.entitybroker.entityprovider.capabilities.ActionsExecutable; +import org.sakaiproject.entitybroker.entityprovider.capabilities.Describeable; +import org.sakaiproject.entitybroker.exception.EntityNotFoundException; +import org.sakaiproject.entitybroker.util.AbstractEntityProvider; +import org.sakaiproject.event.api.Event; +import org.sakaiproject.event.api.EventTrackingService; +import org.sakaiproject.event.api.NotificationEdit; +import org.sakaiproject.event.api.NotificationService; +import org.sakaiproject.user.api.User; +import org.sakaiproject.user.api.UserDirectoryService; +import org.sakaiproject.user.api.UserNotDefinedException; + +import java.util.Map; + +/** + * NotificationEntityProvider exposes a REST api to the notification service at baseURL/PREFIX/post/{userEId} + * + * Added for https://jira.sakaiproject.org/browse/SAK-25733 + * @author Bill Smith (wsmith @ unicon.net) + * @author Aaron Zeckoski (azeckoski @ gmail.com) (azeckoski @ unicon.net) + */ +public class NotificationEntityProvider extends AbstractEntityProvider implements ActionsExecutable, Describeable { + + /** + * Config setting which determines if this is enabled + */ + public static final String NOTIFY_POST_ENABLED = "notify.post.enabled"; + public static final String PREFIX = "notify"; + + private static Log log = LogFactory.getLog(NotificationEntityProvider.class); + + private EventTrackingService eventTrackingService; + private NotificationService notificationService; + private UserDirectoryService userDirectoryService; + + ExternalEmailNotification emailNotification; + + public String getEntityPrefix() { + return PREFIX; + } + + /** + * Spring INIT method, runs on start + */ + public void init() { + log.info("INIT: enabled="+isEnabled()); + if (isEnabled()) { + // don't bother loading up this stuff unless the notification is enabled + NotificationEdit edit = notificationService.addTransientNotification(); + edit.setFunction("rest.notify.post"); + emailNotification = new ExternalEmailNotification(); + edit.setAction(emailNotification); + } else { + emailNotification = null; + } + } + + /** + * Determine if the request is valid/authorized + * + * @return true if the request is valid/authorized. + * @throws SecurityException if is not authorized + */ + private boolean isAuthorized() { + boolean authorized = false; + if (developerHelperService.isUserAdmin(developerHelperService.getCurrentUserReference())) { + authorized = true; + } else { + throw new SecurityException("Only a sakai super user can access this service: user="+developerHelperService.getCurrentUserId()); + } + return authorized; + } + + /** + * Returns the enabled status of this service. This will eventually be used to turn the service on/off. + * + * @return true if the service is enabled. + */ + public boolean isEnabled() { + boolean notifyPostEnabled = developerHelperService.getConfigurationSetting(NOTIFY_POST_ENABLED, false); + return notifyPostEnabled; + } + + /** + * Handles a request to create a new notification. Expects /notify/post/{userEId} + * + * @param view + * @param params + * @return + */ + @EntityCustomAction(action = "post", viewKey = EntityView.VIEW_NEW) + public void postNotification(EntityView view, Map params) { + if (log.isDebugEnabled()) { + log.debug("postNotification params: " + params); + } + if (!isEnabled()) { + throw new IllegalStateException("Service is disabled. Check your configuration ("+NOTIFY_POST_ENABLED+" must be true)!"); + } else { + isAuthorized(); // does check and throws exception if fails to pass + + String userEId = getUserEId(view); + User user = getUserByEId(userEId); + String notification = getNotification(params); + + emailNotification.addMessage(notification); + Event event = eventTrackingService.newEvent("rest.notify.post", null, user.getId(), false, NotificationService.NOTI_OPTIONAL); + eventTrackingService.post(event); + } + } + + + /** + * Get the notification from the request params + * @param params request params + * @return the notification string + * @throws IllegalArgumentException if not found + */ + private String getNotification(Map params) { + String notification = (String) params.get("notification"); + if (notification == null || "".equals(notification)) { + throw new IllegalArgumentException("Notification cannot be empty"); + } + return notification; + } + + /** + * Attempts to get the user by EID + * + * @param userEId + * the EID of the user to retrieve + * @return the specified user + * @throws UserNotDefinedException + * if the user was not found + */ + private User getUserByEId(String userEId) throws EntityNotFoundException { + log.debug("Getting user for id: " + userEId); + User user; + try { + user = userDirectoryService.getUserByEid(userEId); + } catch (UserNotDefinedException e) { + throw new EntityNotFoundException(e + ": could not find user for specified id. ", userEId); + } + return user; + } + + /** + * Extract the user eid from the view + * @param view the entity view + * @return user eid + * @throws IllegalArgumentException if the user eid cannot be found + */ + private String getUserEId(EntityView view) { + String userEId = view.getPathSegment(2); + if (userEId == null || "".equals(userEId)) { + throw new IllegalArgumentException("User id cannot be empty"); + } + return userEId; + } + + public void setEventTrackingService(EventTrackingService eventTrackingService) { + this.eventTrackingService = eventTrackingService; + } + + public void setNotificationService(NotificationService notificationService) { + this.notificationService = notificationService; + } + + public void setUserDirectoryService(UserDirectoryService userDirectoryService) { + this.userDirectoryService = userDirectoryService; + } + +} \ No newline at end of file Property changes on: core-providers/src/java/org/sakaiproject/entitybroker/providers/NotificationEntityProvider.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: core-providers/src/java/org/sakaiproject/entitybroker/providers/user/prefs/ExternalEmailUserNotificationPreferencesRegistrationImpl.java =================================================================== --- core-providers/src/java/org/sakaiproject/entitybroker/providers/user/prefs/ExternalEmailUserNotificationPreferencesRegistrationImpl.java (revision 0) +++ core-providers/src/java/org/sakaiproject/entitybroker/providers/user/prefs/ExternalEmailUserNotificationPreferencesRegistrationImpl.java (working copy) @@ -0,0 +1,64 @@ +/** + * $Id$ + * $URL$ + ************************************************************************** + * Copyright (c) 2014 The Sakai Foundation + * + * Licensed under the Educational Community License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.opensource.org/licenses/ECL-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sakaiproject.entitybroker.providers.user.prefs; + +import org.sakaiproject.entitybroker.DeveloperHelperService; +import org.sakaiproject.util.ResourceLoader; +import org.sakaiproject.util.UserNotificationPreferencesRegistrationImpl; + +/** + * Adds external email notification user preferences to the user notification preferences page. + * + * Added for https://jira.sakaiproject.org/browse/SAK-25733 + * @author Bill Smith (wsmith @ unicon.net) + * @author Aaron Zeckoski (azeckoski @ gmail.com) (azeckoski @ unicon.net) + */ +public class ExternalEmailUserNotificationPreferencesRegistrationImpl extends + UserNotificationPreferencesRegistrationImpl { + + /** + * Config setting which determines if this is enabled + */ + public static final String NOTIFY_POST_ENABLED = "notify.post.enabled"; + + private DeveloperHelperService developerHelperService; + + /* (non-Javadoc) + * @see org.sakaiproject.user.api.UserNotificationPreferencesRegistration#getResourceLoader(java.lang.String) + */ + public ResourceLoader getResourceLoader(String location) { + return new ResourceLoader(location); + } + + /** + * Conditionally enables the user preferences for external email notification posting. + * @see org.sakaiproject.entitybroker.providers.NotificationEntityProvider#isEnabled + */ + public void init() { + if (developerHelperService.getConfigurationSetting(NOTIFY_POST_ENABLED, false)) { + // only run the init when this is enabled + super.init(); + } + } + + public void setDeveloperHelperService(DeveloperHelperService developerHelperService) { + this.developerHelperService = developerHelperService; + } +} \ No newline at end of file Property changes on: core-providers/src/java/org/sakaiproject/entitybroker/providers/user/prefs/ExternalEmailUserNotificationPreferencesRegistrationImpl.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: core-providers/src/webapp/WEB-INF/applicationContext.xml =================================================================== --- core-providers/src/webapp/WEB-INF/applicationContext.xml (revision 306259) +++ core-providers/src/webapp/WEB-INF/applicationContext.xml (working copy) @@ -42,5 +42,37 @@ + + + + + + + + + notify + prefs_title + prefs_description + prefs_title_override + 3 + sakai:external + ext + sakai.externalnoti + + + prefs_opt1 + prefs_opt2 + prefs_opt3 + + + false + true + +