forked from DSpace/DSpace
-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request DSpace#8922 from mwoodiupui/8500
Missing subject in template-based e-mails
- Loading branch information
Showing
1 changed file
with
109 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,6 @@ | |
import java.util.Collections; | ||
import java.util.Date; | ||
import java.util.Enumeration; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.Properties; | ||
import javax.activation.DataHandler; | ||
|
@@ -41,7 +40,6 @@ | |
import javax.mail.internet.MimeMultipart; | ||
import javax.mail.internet.ParseException; | ||
|
||
import org.apache.commons.lang3.StringUtils; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.apache.velocity.Template; | ||
|
@@ -57,26 +55,40 @@ | |
import org.dspace.services.factory.DSpaceServicesFactory; | ||
|
||
/** | ||
* Class representing an e-mail message, also used to send e-mails. | ||
* Class representing an e-mail message. The {@link send} method causes the | ||
* assembled message to be formatted and sent. | ||
* <p> | ||
* Typical use: | ||
* </p> | ||
* <pre> | ||
* <code>Email email = Email.getEmail(path);</code> | ||
* <code>email.addRecipient("[email protected]");</code> | ||
* <code>email.addArgument("John");</code> | ||
* <code>email.addArgument("On the Testing of DSpace");</code> | ||
* <code>email.send();</code> | ||
* </pre> | ||
* {@code path} is the filesystem path of an email template, typically in | ||
* {@code ${dspace.dir}/config/emails/} and can include the subject -- see | ||
* below. Templates are processed by <a href='https://velocity.apache.org/'> | ||
* Apache Velocity</a>. They may contain VTL directives and property | ||
* placeholders. | ||
* <p> | ||
* {@link addArgument(string)} adds a property to the {@code params} array | ||
* in the Velocity context, which can be used to replace placeholder tokens | ||
* in the message. These arguments are indexed by number in the order they were | ||
* added to the message. | ||
* <p> | ||
* The DSpace configuration properties are also available to templates as the | ||
* array {@code config}, indexed by name. Example: {@code ${config.get('dspace.name')}} | ||
* <p> | ||
* Recipients and attachments may be added as needed. See {@link addRecipient}, | ||
* {@link addAttachment(File, String)}, and | ||
* {@link addAttachment(InputStream, String, String)}. | ||
* <p> | ||
* <code>Email email = new Email();</code><br> | ||
* <code>email.addRecipient("[email protected]");</code><br> | ||
* <code>email.addArgument("John");</code><br> | ||
* <code>email.addArgument("On the Testing of DSpace");</code><br> | ||
* <code>email.send();</code><br> | ||
* </p> | ||
* Headers such as Subject may be supplied by the template, by defining them | ||
* using the VTL directive {@code #set()}. Only headers named in the DSpace | ||
* configuration array property {@code mail.message.headers} will be added. | ||
* <p> | ||
* <code>name</code> is the name of an email template in | ||
* <code>dspace-dir/config/emails/</code> (which also includes the subject.) | ||
* <code>arg0</code> and <code>arg1</code> are arguments to fill out the | ||
* message with. | ||
* <P> | ||
* Emails are formatted using Apache Velocity. Headers such as Subject may be | ||
* supplied by the template, by defining them using #set(). Example: | ||
* </p> | ||
* Example: | ||
* | ||
* <pre> | ||
* | ||
|
@@ -91,12 +103,14 @@ | |
* | ||
* Thank you for sending us your submission "${params[1]}". | ||
* | ||
* -- | ||
* The ${config.get('dspace.name')} Team | ||
* | ||
* </pre> | ||
* | ||
* <p> | ||
* If the example code above was used to send this mail, the resulting mail | ||
* would have the subject <code>Example e-mail</code> and the body would be: | ||
* </p> | ||
* | ||
* <pre> | ||
* | ||
|
@@ -105,7 +119,16 @@ | |
* | ||
* Thank you for sending us your submission "On the Testing of DSpace". | ||
* | ||
* -- | ||
* The DSpace Team | ||
* | ||
* </pre> | ||
* <p> | ||
* There are two ways to load a message body. One can create an instance of | ||
* {@link Email} and call {@link setContent} on it, passing the body as a String. Or | ||
* one can use the static factory method {@link getEmail} to load a file by its | ||
* complete filesystem path. In either case the text will be loaded into a | ||
* Velocity template. | ||
* | ||
* @author Robert Tansley | ||
* @author Jim Downing - added attachment handling code | ||
|
@@ -115,7 +138,6 @@ public class Email { | |
/** | ||
* The content of the message | ||
*/ | ||
private String content; | ||
private String contentName; | ||
|
||
/** | ||
|
@@ -176,13 +198,12 @@ public Email() { | |
moreAttachments = new ArrayList<>(10); | ||
subject = ""; | ||
template = null; | ||
content = ""; | ||
replyTo = null; | ||
charset = null; | ||
} | ||
|
||
/** | ||
* Add a recipient | ||
* Add a recipient. | ||
* | ||
* @param email the recipient's email address | ||
*/ | ||
|
@@ -196,16 +217,24 @@ public void addRecipient(String email) { | |
* "Subject:" line must be stripped. | ||
* | ||
* @param name a name for this message body | ||
* @param cnt the content of the message | ||
* @param content the content of the message | ||
*/ | ||
public void setContent(String name, String cnt) { | ||
content = cnt; | ||
public void setContent(String name, String content) { | ||
contentName = name; | ||
arguments.clear(); | ||
|
||
VelocityEngine templateEngine = new VelocityEngine(); | ||
templateEngine.init(VELOCITY_PROPERTIES); | ||
|
||
StringResourceRepository repo = (StringResourceRepository) | ||
templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME); | ||
repo.putStringResource(contentName, content); | ||
// Turn content into a template. | ||
template = templateEngine.getTemplate(contentName); | ||
} | ||
|
||
/** | ||
* Set the subject of the message | ||
* Set the subject of the message. | ||
* | ||
* @param s the subject of the message | ||
*/ | ||
|
@@ -214,7 +243,7 @@ public void setSubject(String s) { | |
} | ||
|
||
/** | ||
* Set the reply-to email address | ||
* Set the reply-to email address. | ||
* | ||
* @param email the reply-to email address | ||
*/ | ||
|
@@ -223,21 +252,39 @@ public void setReplyTo(String email) { | |
} | ||
|
||
/** | ||
* Fill out the next argument in the template | ||
* Fill out the next argument in the template. | ||
* | ||
* @param arg the value for the next argument | ||
*/ | ||
public void addArgument(Object arg) { | ||
arguments.add(arg); | ||
} | ||
|
||
/** | ||
* Add an attachment bodypart to the message from an external file. | ||
* | ||
* @param f reference to a file to be attached. | ||
* @param name a name for the resulting bodypart in the message's MIME | ||
* structure. | ||
*/ | ||
public void addAttachment(File f, String name) { | ||
attachments.add(new FileAttachment(f, name)); | ||
} | ||
|
||
/** When given a bad MIME type for an attachment, use this instead. */ | ||
private static final String DEFAULT_ATTACHMENT_TYPE = "application/octet-stream"; | ||
|
||
/** | ||
* Add an attachment bodypart to the message from a byte stream. | ||
* | ||
* @param is the content of this stream will become the content of the | ||
* bodypart. | ||
* @param name a name for the resulting bodypart in the message's MIME | ||
* structure. | ||
* @param mimetype the MIME type of the resulting bodypart, such as | ||
* "text/pdf". If {@code null} it will default to | ||
* "application/octet-stream", which is MIME for "unknown format". | ||
*/ | ||
public void addAttachment(InputStream is, String name, String mimetype) { | ||
if (null == mimetype) { | ||
LOG.error("Null MIME type replaced with '" + DEFAULT_ATTACHMENT_TYPE | ||
|
@@ -257,6 +304,11 @@ public void addAttachment(InputStream is, String name, String mimetype) { | |
moreAttachments.add(new InputStreamAttachment(is, name, mimetype)); | ||
} | ||
|
||
/** | ||
* Set the character set of the message. | ||
* | ||
* @param cs the name of a character set, such as "UTF-8" or "EUC-JP". | ||
*/ | ||
public void setCharset(String cs) { | ||
charset = cs; | ||
} | ||
|
@@ -280,15 +332,20 @@ public void reset() { | |
* {@code mail.message.headers} then that name and its value will be added | ||
* to the message's headers. | ||
* | ||
* <p>"subject" is treated specially: if {@link setSubject()} has not been called, | ||
* the value of any "subject" property will be used as if setSubject had | ||
* been called with that value. Thus a template may define its subject, but | ||
* the caller may override it. | ||
* <p>"subject" is treated specially: if {@link setSubject()} has not been | ||
* called, the value of any "subject" property will be used as if setSubject | ||
* had been called with that value. Thus a template may define its subject, | ||
* but the caller may override it. | ||
* | ||
* @throws MessagingException if there was a problem sending the mail. | ||
* @throws IOException if IO error | ||
*/ | ||
public void send() throws MessagingException, IOException { | ||
if (null == template) { | ||
// No template -- no content -- PANIC!!! | ||
throw new MessagingException("Email has no body"); | ||
} | ||
|
||
ConfigurationService config | ||
= DSpaceServicesFactory.getInstance().getConfigurationService(); | ||
|
||
|
@@ -308,37 +365,18 @@ public void send() throws MessagingException, IOException { | |
MimeMessage message = new MimeMessage(session); | ||
|
||
// Set the recipients of the message | ||
Iterator<String> i = recipients.iterator(); | ||
|
||
while (i.hasNext()) { | ||
message.addRecipient(Message.RecipientType.TO, new InternetAddress( | ||
i.next())); | ||
for (String recipient : recipients) { | ||
message.addRecipient(Message.RecipientType.TO, | ||
new InternetAddress(recipient)); | ||
} | ||
// Get headers defined by the template. | ||
String[] templateHeaders = config.getArrayProperty("mail.message.headers"); | ||
|
||
// Format the mail message body | ||
VelocityEngine templateEngine = new VelocityEngine(); | ||
templateEngine.init(VELOCITY_PROPERTIES); | ||
|
||
VelocityContext vctx = new VelocityContext(); | ||
vctx.put("config", new UnmodifiableConfigurationService(config)); | ||
vctx.put("params", Collections.unmodifiableList(arguments)); | ||
|
||
if (null == template) { | ||
if (StringUtils.isBlank(content)) { | ||
// No template and no content -- PANIC!!! | ||
throw new MessagingException("Email has no body"); | ||
} | ||
// No template, so use a String of content. | ||
StringResourceRepository repo = (StringResourceRepository) | ||
templateEngine.getApplicationAttribute(RESOURCE_REPOSITORY_NAME); | ||
repo.putStringResource(contentName, content); | ||
// Turn content into a template. | ||
template = templateEngine.getTemplate(contentName); | ||
templateHeaders = new String[] {}; | ||
} | ||
|
||
StringWriter writer = new StringWriter(); | ||
try { | ||
template.merge(vctx, writer); | ||
|
@@ -405,7 +443,8 @@ public void send() throws MessagingException, IOException { | |
// add the stream | ||
messageBodyPart = new MimeBodyPart(); | ||
messageBodyPart.setDataHandler(new DataHandler( | ||
new InputStreamDataSource(attachment.name,attachment.mimetype,attachment.is))); | ||
new InputStreamDataSource(attachment.name, | ||
attachment.mimetype, attachment.is))); | ||
messageBodyPart.setFileName(attachment.name); | ||
multipart.addBodyPart(messageBodyPart); | ||
} | ||
|
@@ -447,6 +486,9 @@ public void send() throws MessagingException, IOException { | |
/** | ||
* Get the VTL template for an email message. The message is suitable | ||
* for inserting values using Apache Velocity. | ||
* <p> | ||
* Note that everything is stored here, so that only send() throws a | ||
* MessagingException. | ||
* | ||
* @param emailFile | ||
* full name for the email template, for example "/dspace/config/emails/register". | ||
|
@@ -484,15 +526,6 @@ public static Email getEmail(String emailFile) | |
} | ||
return email; | ||
} | ||
/* | ||
* Implementation note: It might be necessary to add a quick utility method | ||
* like "send(to, subject, message)". We'll see how far we get without it - | ||
* having all emails as templates in the config allows customisation and | ||
* internationalisation. | ||
* | ||
* Note that everything is stored and the run in send() so that only send() | ||
* throws a MessagingException. | ||
*/ | ||
|
||
/** | ||
* Test method to send an email to check email server settings | ||
|
@@ -547,7 +580,7 @@ public static void main(String[] args) { | |
} | ||
|
||
/** | ||
* Utility struct class for handling file attachments. | ||
* Utility record class for handling file attachments. | ||
* | ||
* @author ojd20 | ||
*/ | ||
|
@@ -563,7 +596,7 @@ public FileAttachment(File f, String n) { | |
} | ||
|
||
/** | ||
* Utility struct class for handling file attachments. | ||
* Utility record class for handling file attachments. | ||
* | ||
* @author Adán Román Ruiz at arvo.es | ||
*/ | ||
|
@@ -580,13 +613,23 @@ public InputStreamAttachment(InputStream is, String name, String mimetype) { | |
} | ||
|
||
/** | ||
* Wrap an {@link InputStream} in a {@link DataSource}. | ||
* | ||
* @author arnaldo | ||
*/ | ||
public static class InputStreamDataSource implements DataSource { | ||
private final String name; | ||
private final String contentType; | ||
private final ByteArrayOutputStream baos; | ||
|
||
/** | ||
* Consume the content of an InputStream and store it in a local buffer. | ||
* | ||
* @param name give the DataSource a name. | ||
* @param contentType the DataSource contains this type of data. | ||
* @param inputStream content to be buffered in the DataSource. | ||
* @throws IOException if the stream cannot be read. | ||
*/ | ||
InputStreamDataSource(String name, String contentType, InputStream inputStream) throws IOException { | ||
this.name = name; | ||
this.contentType = contentType; | ||
|