/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/emailtemplateservice/trunk/impl/logic/src/java/org/sakaiproject/emailtemplateservice/service/impl/EmailTemplateServiceImpl.java $
* $Id: EmailTemplateServiceImpl.java 122193 2013-04-04 16:26:12Z matthew@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.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.emailtemplateservice.service.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
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.Map.Entry;
import java.util.Set;
import org.apache.commons.lang.LocaleUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.entitybroker.DeveloperHelperService;
import org.sakaiproject.genericdao.api.search.Restriction;
import org.sakaiproject.genericdao.api.search.Search;
import org.sakaiproject.tool.api.Session;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.user.api.PreferencesService;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserDirectoryService;
import org.simpleframework.xml.core.Persister;
import org.springframework.dao.DataIntegrityViolationException;
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;
}
private EmailTemplate getEmailTemplateNoDefault(String key, Locale locale) {
log.debug("getEmailTemplateNoDefault( " + key +"," + locale);
if (key == null || "".equals(key)) {
throw new IllegalArgumentException("key cannot be null or empty");
}
EmailTemplate et = null;
if (locale != null) {
Search search = new Search("key", key);
search.addRestriction( new Restriction("locale", locale.toString()) );
et = dao.findOneBySearch(EmailTemplate.class, search);
} else {
Search search = new Search("key", key);
search.addRestriction( new Restriction("locale", EmailTemplate.DEFAULT_LOCALE) );
et = dao.findOneBySearch(EmailTemplate.class, search);
}
return et;
}
public EmailTemplate getEmailTemplate(String key, Locale locale) {
if (key == null || "".equals(key)) {
throw new IllegalArgumentException("key cannot be null or empty");
}
if(log.isDebugEnabled()) {
log.debug("getEmailTemplate(key=" + key + ", locale=" + locale + ")");
}
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);
}
}
if (et == null) {
Search search = new Search("key", key);
search.addRestriction( new Restriction("locale", EmailTemplate.DEFAULT_LOCALE) );
et = dao.findOneBySearch(EmailTemplate.class, search);
}
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);
if (locale == null) {
search.addRestriction( new Restriction("locale", EmailTemplate.DEFAULT_LOCALE));
} else {
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 is optional, so might be null or empty
if (ret.getHtmlMessage() != null && !ret.getHtmlMessage().trim().isEmpty())
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) {
//check that fields are set
if (template == null) {
throw new IllegalArgumentException("Template can't be null");
}
if (template.getKey() == null) {
throw new IllegalArgumentException("Template key can't be null");
}
if (template.getOwner() == null) {
throw new IllegalArgumentException("Template owner can't be null");
}
if (template.getSubject() == null) {
throw new IllegalArgumentException("Template subject can't be null");
}
if (template.getMessage() == null) {
throw new IllegalArgumentException("Template message can't be null");
}
String locale = template.getLocale();
if (locale == null || locale.trim().length() == 0) {
//For backward compatibility set it to default
template.setLocale(EmailTemplate.DEFAULT_LOCALE);
}
//update the modified date
template.setLastModified(new Date());
try {
dao.save(template);
}
catch (DataIntegrityViolationException die) {
throw new IllegalArgumentException("Key: " + template.getKey() + " and locale: " + template.getLocale() + " in use already", die);
}
log.info("saved template: " + template.getId());
}
public void updateTemplate(EmailTemplate template) {
template.setLastModified(new Date());
String locale = template.getLocale();
if (locale == null || "".equals(locale)) {
template.setLocale(EmailTemplate.DEFAULT_LOCALE);
}
dao.update(template);
log.info("updated template: " + template.getId());
}
protected Locale getUserLocale(String userId) {
Locale loc = preferencesService.getLocale(userId);
//the user has no preference set - get the system default
if (loc == null ) {
loc = Locale.getDefault();
}
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 construct the headers
List<String> headers = new ArrayList<String>();
//the template may specify a from address
if (StringUtils.isNotBlank(rt.getFrom())) {
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 + ">" );
//SAK-21742 we need the rendered subject
headers.add("Subject: " + rt.getRenderedSubject());
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 references
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
//The locale could also be the Default
Locale loc = null;
if (template.getLocale() != null && !"".equals(template.getLocale()) && !EmailTemplate.DEFAULT_LOCALE.equals(template.getLocale())) {
loc = LocaleUtils.toLocale(template.getLocale());
}
EmailTemplate existingTemplate = getEmailTemplateNoDefault(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());
}
}
}
public String exportTemplateAsXml(String key, Locale locale) {
EmailTemplate template = getEmailTemplate(key, locale);
Persister persister = new Persister();
File file = null;
String ret = null;
try {
file = File.createTempFile("emailtemplate", "xml");
persister.write(template, file);
//read the data
ret = readFile(file.getAbsolutePath());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
if (file != null) {
if (!file.delete()) {
log.warn("error deleting tmp file");
}
}
}
return ret;
}
private static String readFile(String path) throws IOException {
FileInputStream stream = new FileInputStream(new File(path));
try {
FileChannel fc = stream.getChannel();
MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
/* Instead of using default, pass in a decoder. */
return Charset.defaultCharset().decode(bb).toString();
}
finally {
stream.close();
}
}
/**
* Delete all templates in the Database
* Only used in unit tests so not in API
* TODO rewrite for efficiency
*/
public void deleteAllTemplates() {
log.debug("deleteAllTemplates");
List<EmailTemplate> templates = dao.findAll(EmailTemplate.class);
for (int i =0; i < templates.size(); i++) {
EmailTemplate template = templates.get(i);
log.debug("deleting template: " + template.getId());
dao.delete(template);
}
}
}