/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/emailtemplateservice/tags/emailtemplateservice-0.5.4/impl/logic/src/java/org/sakaiproject/emailtemplateservice/service/impl/EmailTemplateServiceImpl.java $ * $Id: EmailTemplateServiceImpl.java 98753 2011-09-29 13:45:54Z ottenhoff@longsight.com $ *********************************************************************************** * * Copyright 2006, 2007 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.osedu.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.emailtemplateservice.service.impl; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import java.io.InputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.email.api.EmailService; import org.sakaiproject.emailtemplateservice.dao.impl.EmailTemplateServiceDao; import org.sakaiproject.emailtemplateservice.model.EmailTemplate; import org.sakaiproject.emailtemplateservice.model.EmailTemplateLocaleUsers; import org.sakaiproject.emailtemplateservice.model.RenderedTemplate; import org.sakaiproject.emailtemplateservice.service.EmailTemplateService; import org.sakaiproject.emailtemplateservice.util.TextTemplateLogicUtils; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.entitybroker.DeveloperHelperService; import org.sakaiproject.genericdao.api.search.Restriction; import org.sakaiproject.genericdao.api.search.Search; import org.sakaiproject.i18n.InternationalizedMessages; import org.sakaiproject.user.api.Preferences; import org.sakaiproject.user.api.PreferencesService; import org.sakaiproject.user.api.User; import org.sakaiproject.user.api.UserDirectoryService; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.tool.api.Session; import org.simpleframework.xml.core.Persister; public class EmailTemplateServiceImpl implements EmailTemplateService { private static Log log = LogFactory.getLog(EmailTemplateServiceImpl.class); private EmailTemplateServiceDao dao; public void setDao(EmailTemplateServiceDao d) { dao = d; } private DeveloperHelperService developerHelperService; public void setDeveloperHelperService(DeveloperHelperService developerHelperService) { this.developerHelperService = developerHelperService; } private PreferencesService preferencesService; public void setPreferencesService(PreferencesService ps) { preferencesService = ps; } private ServerConfigurationService serverConfigurationService; public void setServerConfigurationService(ServerConfigurationService scs) { serverConfigurationService = scs; } private SessionManager sessionManager; public void setSessionManager(SessionManager sm) { sessionManager = sm; } public EmailTemplate getEmailTemplateById(Long id) { if (id == null) { throw new IllegalArgumentException("id cannot be null or empty"); } EmailTemplate et = dao.findById(EmailTemplate.class, id); return et; } private EmailService emailService; public void setEmailService(EmailService emailService) { this.emailService = emailService; } private UserDirectoryService userDirectoryService; public void setUserDirectoryService(UserDirectoryService userDirectoryService) { this.userDirectoryService = userDirectoryService; } public EmailTemplate getEmailTemplate(String key, Locale locale) { if (key == null || "".equals(key)) { throw new IllegalArgumentException("key cannot be null or empty"); } EmailTemplate et = null; // TODO make this more efficient if (locale != null) { Search search = new Search("key", key); search.addRestriction( new Restriction("locale", locale.toString()) ); et = dao.findOneBySearch(EmailTemplate.class, search); if (et == null) { search.addRestriction( new Restriction("locale", locale.getLanguage()) ); et = dao.findOneBySearch(EmailTemplate.class, search); } } else if (et == null) { et = dao.findOneBySearch(EmailTemplate.class, new Search("key", key)); } if (et == null) { log.warn("no template found for: " + key + " in locale " + locale ); } return et; } public boolean templateExists(String key, Locale locale) { List<EmailTemplate> et = null; Search search = new Search("key", key); search.addRestriction( new Restriction("locale", locale.toString()) ); et = dao.findBySearch(EmailTemplate.class, search); if (et != null && et.size() > 0) { return true; } return false; } public List<EmailTemplate> getEmailTemplates(int max, int start) { return dao.findAll(EmailTemplate.class, start, max); } public RenderedTemplate getRenderedTemplate(String key, Locale locale, Map<String, String> replacementValues) { EmailTemplate temp = getEmailTemplate(key, locale); //if no template was found we need to return null to avoid an NPE if (temp == null) return null; RenderedTemplate ret = new RenderedTemplate(temp); //get the default current user fields log.debug("getting default values"); Map<String, String> userVals = getCurrentUserFields(); replacementValues.putAll(userVals); log.debug("got replacement values"); ret.setRenderedSubject(this.processText(ret.getSubject(), replacementValues, key)); ret.setRenderedMessage(this.processText(ret.getMessage(), replacementValues, key)); //HTML component might be null if (ret.getHtmlMessage() != null) ret.setRenderedHtmlMessage(this.processText(ret.getHtmlMessage(), replacementValues, key)); return ret; } public RenderedTemplate getRenderedTemplateForUser(String key, String userReference, Map<String, String> replacementValues) { log.debug("getRenderedTemplateForUser(" + key + ", " +userReference); String userId = developerHelperService.getUserIdFromRef(userReference); Locale loc = getUserLocale(userId); return getRenderedTemplate(key,loc,replacementValues); } public void saveTemplate(EmailTemplate template) { //update the modified date template.setLastModified(new Date()); dao.save(template); log.info("saved template: " + template.getId()); } public void updateTemplate(EmailTemplate template) { template.setLastModified(new Date()); dao.update(template); log.info("updated template: " + template.getId()); } protected Locale getUserLocale(String userId) { Locale loc = null; Preferences prefs = preferencesService.getPreferences(userId); ResourceProperties locProps = prefs.getProperties(InternationalizedMessages.APPLICATION_ID); String localeString = locProps.getProperty(InternationalizedMessages.LOCALE_KEY); if (localeString != null) { String[] locValues = localeString.split("_"); if (locValues.length > 1) loc = new Locale(locValues[0], locValues[1]); // language, country else if (locValues.length == 1) loc = new Locale(locValues[0]); // just language } //the user has no preference set - get the system default if (loc == null ) { String lang = System.getProperty("user.language"); String region = System.getProperty("user.region"); if (region != null) { log.debug("getting system locale for: " + lang + "_" + region); loc = new Locale(lang,region); } else { log.debug("getting system locale for: " + lang ); loc = new Locale(lang); } } return loc; } protected String processText(String text, Map<String, String> values, String templateName) { return TextTemplateLogicUtils.processTextTemplate(text, values, templateName); } protected Map<String, String> getCurrentUserFields() { Map<String, String> rv = new HashMap<String, String>(); String userRef = developerHelperService.getCurrentUserReference(); if (userRef != null) { User user = (User) developerHelperService.fetchEntity(userRef); try { String email = user.getEmail(); if (email == null) email = ""; String first = user.getFirstName(); if (first == null) first = ""; String last = user.getLastName(); if (last == null) last =""; rv.put(CURRENT_USER_EMAIL, email); rv.put(CURRENT_USER_FIRST_NAME, first); rv.put(CURRENT_USER_LAST_NAME, last); rv.put(CURRENT_USER_DISPLAY_NAME, user.getDisplayName()); rv.put(CURRENT_USER_DISPLAY_ID, user.getDisplayId()); rv.put("currentUserDispalyId", user.getDisplayId()); } catch (Exception e) { log.warn("Failed to get current user replacements: " + userRef, e); } } /*NoN user fields */ rv.put(LOCAL_SAKAI_NAME, serverConfigurationService.getString("ui.service", "Sakai")); rv.put(LOCAL_SAKAI_SUPPORT_MAIL,serverConfigurationService.getString("support.email","help@"+ serverConfigurationService.getServerUrl())); rv.put(LOCAL_SAKAI_URL,serverConfigurationService.getServerUrl()); return rv; } public Map<EmailTemplateLocaleUsers, RenderedTemplate> getRenderedTemplates( String key, List<String> userReferences, Map<String, String> replacementValues) { List<Locale> foundLocales = new ArrayList<Locale>(); Map<Locale, EmailTemplateLocaleUsers> mapStore = new HashMap<Locale, EmailTemplateLocaleUsers>(); for (int i = 0; i < userReferences.size(); i++) { String userReference = userReferences.get(i); String userId = developerHelperService.getUserIdFromRef(userReference); Locale loc = getUserLocale(userId); //have we found this locale? if (! foundLocales.contains(loc)) { //create a new EmailTemplateLocalUser EmailTemplateLocaleUsers etlu = new EmailTemplateLocaleUsers(); log.debug("adding users " + userReference + " to new object"); etlu.setLocale(loc); etlu.addUser(userReference); mapStore.put(loc, etlu); foundLocales.add(loc); } else { EmailTemplateLocaleUsers etlu = mapStore.get(loc); log.debug("adding users " + userReference + " to existing object"); etlu.addUser(userReference); mapStore.remove(loc); mapStore.put(loc, etlu); } } Map<EmailTemplateLocaleUsers, RenderedTemplate> ret = new HashMap<EmailTemplateLocaleUsers, RenderedTemplate>(); //now for each locale we need a rendered template Set<Entry<Locale, EmailTemplateLocaleUsers>> es = mapStore.entrySet(); Iterator<Entry<Locale, EmailTemplateLocaleUsers>> it = es.iterator(); while (it.hasNext()) { Entry<Locale, EmailTemplateLocaleUsers> entry = it.next(); Locale loc = entry.getKey(); RenderedTemplate rt = this.getRenderedTemplate(key, loc, replacementValues); if (rt != null) { ret.put(entry.getValue(), rt); } else { log.error("No template found for key: " + key + " in locale: " + loc); } } return ret; } private final String MULTIPART_BOUNDARY = "======sakai-multi-part-boundary======"; private final String BOUNDARY_LINE = "\n\n--"+MULTIPART_BOUNDARY+"\n"; private final String TERMINATION_LINE = "\n\n--"+MULTIPART_BOUNDARY+"--\n\n"; private final String MIME_ADVISORY = "This message is for MIME-compliant mail readers."; public void sendRenderedMessages(String key, List<String> userReferences, Map<String, String> replacementValues, String fromEmail, String fromName) { Map<EmailTemplateLocaleUsers, RenderedTemplate> tMap = this.getRenderedTemplates(key, userReferences, replacementValues); Set<Entry<EmailTemplateLocaleUsers, RenderedTemplate>> set = tMap.entrySet(); Iterator<Entry<EmailTemplateLocaleUsers, RenderedTemplate>> it = set.iterator(); while (it.hasNext()) { Entry<EmailTemplateLocaleUsers, RenderedTemplate> entry = it.next(); RenderedTemplate rt = entry.getValue(); EmailTemplateLocaleUsers etlu = entry.getKey(); List<User> toAddress = getUsersEmail(etlu.getUserIds()); log.info("sending template " + key + " for locale " + etlu.getLocale().toString() + " to " + toAddress.size() + " users"); StringBuilder message = new StringBuilder(); message.append(MIME_ADVISORY); if (rt.getRenderedMessage() != null) { message.append(BOUNDARY_LINE); message.append("Content-Type: text/plain; charset=iso-8859-1\n"); message.append(rt.getRenderedMessage()); } if (rt.getRenderedHtmlMessage() != null) { //append the HMTL part message.append(BOUNDARY_LINE); message.append("Content-Type: text/html; charset=iso-8859-1\n"); message.append(rt.getRenderedHtmlMessage()); } message.append(TERMINATION_LINE); // we need to manually contruct the headers List<String> headers = new ArrayList<String>(); //the template may specify a from address if (rt.getFrom() != null) { headers.add("From: \"" + rt.getFrom() ); } else { headers.add("From: \"" + fromName + "\" <" + fromEmail + ">" ); } // Add a To: header of either the recipient (if only 1), or the sender (if multiple) String toName = fromName; String toEmail = fromEmail; if (toAddress.size() == 1) { User u = toAddress.get(0); toName = u.getDisplayName(); toEmail = u.getEmail(); } headers.add("To: \"" + toName + "\" <" + toEmail + ">" ); headers.add("Subject: " + rt.getSubject()); headers.add("Content-Type: multipart/alternative; boundary=\"" + MULTIPART_BOUNDARY + "\""); headers.add("Mime-Version: 1.0"); headers.add("Precedence: bulk"); String body = message.toString(); log.debug("message body " + body); emailService.sendToUsers(toAddress, headers, body); } } private List<User> getUsersEmail(List<String> userIds) { //we have a group of referenc List<String> ids = new ArrayList<String>(); for (int i = 0; i < userIds.size(); i++) { String userReference = userIds.get(i); String userId = developerHelperService.getUserIdFromRef(userReference); ids.add(userId); } return userDirectoryService.getUsers(ids); } public void processEmailTemplates(List<String> templatePaths) { final String ADMIN = "admin"; Persister persister = new Persister(); for(String templatePath : templatePaths) { log.debug("Processing template: " + templatePath); InputStream in = getClass().getClassLoader().getResourceAsStream(templatePath); if(in == null) { log.warn("Could not load resource from '" + templatePath + "'. Skipping ..."); continue; } EmailTemplate template = null; try { template = persister.read(EmailTemplate.class,in); } catch(Exception e) { log.warn("Error processing template: '" + templatePath + "', " + e.getClass() + ":" + e.getMessage() + ". Skipping ..."); continue; } //check if we have an existing template of this key and locale //its possible the template has no locale set Locale loc = null; if (template.getLocale() != null) { loc = new Locale(template.getLocale()); } EmailTemplate existingTemplate = getEmailTemplate(template.getKey(), loc); if(existingTemplate == null) { //no existing, save this one Session sakaiSession = sessionManager.getCurrentSession(); sakaiSession.setUserId(ADMIN); sakaiSession.setUserEid(ADMIN); saveTemplate(template); sakaiSession.setUserId(null); sakaiSession.setUserId(null); log.info("Saved email template: " + template.getKey() + " with locale: " + template.getLocale()); continue; //skip to next } //check version, if local one newer than persisted, update it - SAK-17679 //also update the locale - SAK-20987 int existingTemplateVersion = existingTemplate.getVersion() != null ? existingTemplate.getVersion().intValue() : 0; if(template.getVersion() > existingTemplateVersion) { existingTemplate.setSubject(template.getSubject()); existingTemplate.setMessage(template.getMessage()); existingTemplate.setHtmlMessage(template.getHtmlMessage()); existingTemplate.setVersion(template.getVersion()); existingTemplate.setOwner(template.getOwner()); existingTemplate.setLocale(template.getLocale()); Session sakaiSession = sessionManager.getCurrentSession(); sakaiSession.setUserId(ADMIN); sakaiSession.setUserEid(ADMIN); updateTemplate(existingTemplate); sakaiSession.setUserId(null); sakaiSession.setUserId(null); log.info("Updated email template: " + template.getKey() + " with locale: " + template.getLocale()); } } } }