Skip to content

Commit

Permalink
Merge pull request DSpace#8922 from mwoodiupui/8500
Browse files Browse the repository at this point in the history
Missing subject in template-based e-mails
  • Loading branch information
tdonohue authored Aug 4, 2023
2 parents 5d4c45c + d939786 commit c82e058
Showing 1 changed file with 109 additions and 66 deletions.
175 changes: 109 additions & 66 deletions dspace-api/src/main/java/org/dspace/core/Email.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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>
*
Expand All @@ -91,12 +103,14 @@
*
* Thank you for sending us your submission &quot;${params[1]}&quot;.
*
* --
* 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>
*
Expand All @@ -105,7 +119,16 @@
*
* Thank you for sending us your submission &quot;On the Testing of DSpace&quot;.
*
* --
* 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
Expand All @@ -115,7 +138,6 @@ public class Email {
/**
* The content of the message
*/
private String content;
private String contentName;

/**
Expand Down Expand Up @@ -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
*/
Expand All @@ -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
*/
Expand All @@ -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
*/
Expand All @@ -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
Expand All @@ -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;
}
Expand All @@ -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();

Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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".
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
*/
Expand All @@ -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
*/
Expand All @@ -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;
Expand Down

0 comments on commit c82e058

Please sign in to comment.