org.sakaiproject.emailtemplateservice.service.impl.EmailTemplateServiceImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of emailtemplateservice-impl Show documentation
Show all versions of emailtemplateservice-impl Show documentation
Emailtemplateservice implementation
The newest version!
/**********************************************************************************
* $URL$
* $Id$
***********************************************************************************
*
* 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.io.UnsupportedEncodingException;
import java.net.URLDecoder;
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.lang.math.NumberUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.sakaiproject.authz.api.SecurityAdvisor;
import org.sakaiproject.authz.api.SecurityAdvisor.SecurityAdvice;
import org.sakaiproject.authz.api.SecurityService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 final Logger LOG = LoggerFactory.getLogger(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;
}
private SecurityService securityService;
public void setSecurityService(SecurityService value)
{
securityService = value;
}
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;
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 et;
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);
return et != null && et.size() > 0;
}
public List getEmailTemplates(int max, int start) {
return dao.findAll(EmailTemplate.class, start, max);
}
public RenderedTemplate getRenderedTemplate(String key, Locale locale, Map 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 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 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 (StringUtils.isBlank(locale)) {
//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 values, String templateName) {
return TextTemplateLogicUtils.processTextTemplate(text, values, templateName);
}
protected Map getCurrentUserFields() {
Map rv = new HashMap<>();
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("mail.support", "support@"+ serverConfigurationService.getServerName()));
rv.put(LOCAL_SAKAI_URL,serverConfigurationService.getServerUrl());
return rv;
}
public Map getRenderedTemplates(
String key, List userReferences, Map replacementValues) {
List foundLocales = new ArrayList<>();
Map mapStore = new HashMap<>();
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 ret = new HashMap<>();
//now for each locale we need a rendered template
Set> es = mapStore.entrySet();
Iterator> it = es.iterator();
while (it.hasNext()) {
Entry 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 userReferences,
Map replacementValues, String fromEmail, String fromName) {
Map tMap = this.getRenderedTemplates(key, userReferences, replacementValues);
Set> set = tMap.entrySet();
Iterator> it = set.iterator();
while (it.hasNext()) {
Entry entry = it.next();
RenderedTemplate rt = entry.getValue();
EmailTemplateLocaleUsers etlu = entry.getKey();
List toAddress = getUsersEmail(etlu.getUserIds());
LOG.info("sending template " + key + " for locale " + etlu.getLocale().toString() + " to " + toAddress.size() + " users");
sendEmailToUsers(toAddress, rt, fromEmail, fromName);
}
}
/**
* method to send email to Users.
* @param toAddress
* @param rt
* @param fromEmail
* @param fromName
*/
private void sendEmailToUsers(List toAddress, RenderedTemplate rt, String fromEmail, String fromName){
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 headers = new ArrayList<>();
//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 getUsersEmail(List userIds) {
//we have a group of references
List ids = new ArrayList<>();
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 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;
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() : 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) {
LOG.warn( "Error creating or writing to file", e );
}
finally {
if (file != null) {
if (!file.delete()) {
LOG.warn("error deleting tmp file");
}
}
}
return ret;
}
private static String readFile(String path) throws IOException {
try (FileInputStream stream = new FileInputStream(new File(path))) {
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();
}
}
/**
* 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 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);
}
}
public void sendMessage(List userIds, List emailAddresses, RenderedTemplate renderedTemplate, String from, String fromName) {
List toAddress = getUsersEmail(userIds);
//if user has changed the email address
if(emailAddresses.size() == 1){
String toEmail = emailAddresses.get(0);
List additionalHeaders = new ArrayList<>();
additionalHeaders.add("Content-Type: text/html; charset=UTF-8");
emailService.send(from, toEmail, renderedTemplate.getRenderedSubject(), renderedTemplate.getRenderedHtmlMessage(), toEmail, from, additionalHeaders );
return;
}
sendEmailToUsers(toAddress, renderedTemplate, from, fromName);
}
/**
* Registers a new template with the service, defined by the given XML file
* @param templateResourceStream the resource stream for the XML file
* @param templateRegistrationKey the key (name) to register the template under
* @return true if the template was registered
*/
@Override
public boolean importTemplateFromXmlFile(InputStream templateResourceStream, String templateRegistrationKey)
{
if (templateResourceStream == null)
{
LOG.error(String.format("Unable to register template under key '%s': Could not load resource, input stream is null.", templateRegistrationKey));
return false;
}
SecurityAdvisor yesMan = (String userId, String function, String reference) -> SecurityAdvice.ALLOWED;
try
{
securityService.pushAdvisor(yesMan);
// Parse the XML, get all the child templates
Document document = new SAXBuilder().build(templateResourceStream);
List childTemplates = document.getRootElement().getChildren("emailTemplate");
// Create and register a template with the service for each one found in the XML file
childTemplates.stream().forEach( (element) -> { xmlToTemplate(element, templateRegistrationKey); } );
}
catch (JDOMException | IOException e)
{
LOG.error(String.format("Error registering template under key '%s': ", templateRegistrationKey), e);
return false;
}
finally
{
securityService.popAdvisor(yesMan);
}
return true;
}
/**
* Extracts the email template fields from the given XML element. Checks
* if the email template already exists; if it does not, it will save
* the template to the service. If it does exist it will update the template if
* and only if the existing version is less than the new version.
* @param xmlTemplate - the XML element containing the email template data
* @param key - the key (name) of the template to be saved to the service
*/
private void xmlToTemplate(Element xmlTemplate, String key)
{
String subject = xmlTemplate.getChildText("subject");
String body = xmlTemplate.getChildText("message");
String bodyHtml = StringUtils.trimToEmpty(xmlTemplate.getChildText("messagehtml"));
String locale = xmlTemplate.getChildText("locale");
String localeLangTag = xmlTemplate.getChildText("localeLangTag");
int version = NumberUtils.toInt(xmlTemplate.getChildText("version"), 1);
Locale loc;
if( StringUtils.isBlank( localeLangTag ) )
{
loc = new Locale( locale );
}
else
{
loc = Locale.forLanguageTag( localeLangTag );
}
// Determine if template already exists
EmailTemplate template = getEmailTemplate(key, loc);
boolean update = true;
if( template == null )
{
update = false;
template = new EmailTemplate();
}
// If the template does not already exist, or the new version is greater than the existing version, proceed...
if( !update || template.getVersion() == null || version > template.getVersion() )
{
// Dump the values from XML into the object
template.setSubject(subject);
template.setMessage(body);
template.setLocale(locale);
template.setKey(key);
template.setVersion(version);
template.setOwner("admin");
try
{
template.setHtmlMessage(URLDecoder.decode(bodyHtml, "utf8"));
}
catch (UnsupportedEncodingException e)
{
template.setHtmlMessage(bodyHtml);
LOG.warn(String.format("Unable to decode body HTML for template %s, reverting to original value.", key), e);
}
// Update or save the template
try
{
if( update )
{
updateTemplate( template );
}
else
{
saveTemplate( template );
}
LOG.info((update ? "Updated " : "Added ") + key + (update ? " in" : " to") + " the email template service.");
}
catch (Exception e)
{
LOG.error("Error "+ (update ? "updating" : "saving") + " template: " + key, e);
}
}
}
}