New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[JENKINS-32301] Support more than one Reply-To Address #26
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ | |
*/ | ||
package jenkins.plugins.mailer.tasks; | ||
|
||
import com.google.common.collect.Lists; | ||
import hudson.model.TaskListener; | ||
import hudson.remoting.Base64; | ||
import hudson.tasks.Mailer; | ||
|
@@ -49,14 +50,17 @@ | |
|
||
import java.io.UnsupportedEncodingException; | ||
import java.security.interfaces.RSAPublicKey; | ||
import java.util.Collection; | ||
import java.util.Date; | ||
import java.util.LinkedHashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.StringTokenizer; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
/** | ||
* Builder for {@link MimeMessage}. This class is NOT thread-safe. | ||
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a> | ||
*/ | ||
public class MimeMessageBuilder { | ||
|
@@ -68,7 +72,7 @@ public class MimeMessageBuilder { | |
private TaskListener listener; | ||
private String defaultSuffix; | ||
private String from; | ||
private String replyTo; | ||
private Set<InternetAddress> replyTo = new LinkedHashSet<InternetAddress>(); | ||
private String subject; | ||
private String body; | ||
private AddressFilter recipientFilter; | ||
|
@@ -81,8 +85,13 @@ public MimeMessageBuilder() { | |
JenkinsLocationConfiguration jlc = JenkinsLocationConfiguration.get(); | ||
if (jlc != null) { | ||
defaultSuffix = Mailer.descriptor().getDefaultSuffix(); | ||
from = jlc.getAdminAddress(); | ||
replyTo = Mailer.descriptor().getReplyToAddress(); | ||
from = JenkinsLocationConfiguration.get().getAdminAddress(); | ||
final String rto = Mailer.descriptor().getReplyToAddress(); | ||
try { | ||
replyTo.addAll(toNormalizedAddresses(rto)); | ||
} catch(UnsupportedEncodingException e) { | ||
logError("Unable to parse Reply-To Addresses " + rto, e); | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -113,10 +122,27 @@ public MimeMessageBuilder setFrom(@Nonnull String from) { | |
} | ||
|
||
public MimeMessageBuilder setReplyTo(@Nonnull String replyTo) { | ||
this.replyTo = replyTo; | ||
try { | ||
final List<InternetAddress> addresses = toNormalizedAddresses(replyTo); | ||
// Done after to leave the current value untouched if there is a parsing error. | ||
this.replyTo.clear(); | ||
this.replyTo.addAll(addresses); | ||
} catch(UnsupportedEncodingException e) { | ||
logError("Unable to parse Reply-To Addresses " + replyTo, e); | ||
} | ||
return this; | ||
} | ||
|
||
public MimeMessageBuilder addReplyTo(@Nonnull String replyTo) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Used only for testing, right? |
||
try { | ||
this.replyTo.addAll(toNormalizedAddresses(replyTo)); | ||
} catch(UnsupportedEncodingException e) { | ||
logError("Unable to parse Reply-To Addresses " + replyTo, e); | ||
} | ||
return this; | ||
} | ||
|
||
|
||
public MimeMessageBuilder setSubject(@Nonnull String subject) { | ||
this.subject = subject; | ||
return this; | ||
|
@@ -178,8 +204,8 @@ public MimeMessage buildMimeMessage() throws MessagingException, UnsupportedEnco | |
addBody(msg); | ||
addRecipients(msg); | ||
|
||
if (StringUtils.isNotBlank(replyTo)) { | ||
msg.setReplyTo(new Address[]{toNormalizedAddress(replyTo)}); | ||
if (!replyTo.isEmpty()) { | ||
msg.setReplyTo(toAddressArray(replyTo)); | ||
} | ||
return msg; | ||
} | ||
|
@@ -199,6 +225,15 @@ private void setJenkinsInstanceIdent(MimeMessage msg) throws MessagingException | |
} | ||
} | ||
|
||
private static Address[] toAddressArray(Collection<InternetAddress> c) { | ||
if (c == null || c.isEmpty()) { | ||
return new Address[0]; | ||
} | ||
final Address[] addresses = new Address[c.size()]; | ||
c.toArray(addresses); | ||
return addresses; | ||
} | ||
|
||
public static void setInReplyTo(@Nonnull MimeMessage msg, @Nonnull String inReplyTo) throws MessagingException { | ||
msg.setHeader("In-Reply-To", inReplyTo); | ||
msg.setHeader("References", inReplyTo); | ||
|
@@ -234,17 +269,28 @@ private void addRecipients(MimeMessage msg) throws UnsupportedEncodingException, | |
addRecipients(msg, cc, Message.RecipientType.CC); | ||
addRecipients(msg, bcc, Message.RecipientType.BCC); | ||
} | ||
|
||
private void addRecipients(MimeMessage msg, Set<InternetAddress> recipientList, Message.RecipientType recipientType) throws UnsupportedEncodingException, MessagingException { | ||
if (recipientList.isEmpty()) { | ||
return; | ||
} | ||
final Collection<InternetAddress> recipients = recipientFilter != null ? recipientFilter.apply(recipientList) : recipientList; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this code related to the PR title? I doubt. Maybe a standalone bugfix There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a small refactor to share the code needed for the PR with the existing one. |
||
msg.setRecipients(recipientType, toAddressArray(recipients)); | ||
} | ||
|
||
if (recipientFilter != null) { | ||
Set<InternetAddress> filteredList = recipientFilter.apply(recipientList); | ||
msg.setRecipients(recipientType, filteredList.toArray(new InternetAddress[filteredList.size()])); | ||
} else { | ||
msg.setRecipients(recipientType, recipientList.toArray(new InternetAddress[recipientList.size()])); | ||
private List<InternetAddress> toNormalizedAddresses(String addresses) throws UnsupportedEncodingException { | ||
final List<InternetAddress> list = Lists.newLinkedList(); | ||
if (StringUtils.isNotBlank(addresses)) { | ||
StringTokenizer tokens = new StringTokenizer(addresses, " \t\n\r\f,"); | ||
while (tokens.hasMoreTokens()) { | ||
String addressToken = tokens.nextToken(); | ||
InternetAddress internetAddress = toNormalizedAddress(addressToken); | ||
if (internetAddress != null) { | ||
list.add(internetAddress); | ||
} | ||
} | ||
} | ||
return list; | ||
} | ||
|
||
private InternetAddress toNormalizedAddress(String address) throws UnsupportedEncodingException { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,14 +51,22 @@ | |
* @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a> | ||
*/ | ||
public class MimeMessageBuilderTest { | ||
/** Address constant A. */ | ||
private static final String A = "tom.aaaa@gmail.com"; | ||
/** Address constant X. */ | ||
private static final String X = "tom.xxxx@gmail.com"; | ||
/** Address constant Y. */ | ||
private static final String Y = "tom.yyyy@gmail.com"; | ||
/** Address constant Z. */ | ||
private static final String Z = "tom.zzzz@gmail.com"; | ||
|
||
@Rule | ||
public JenkinsRule jenkinsRule = new JenkinsRule(); | ||
|
||
@Before | ||
public void setup() { | ||
JenkinsLocationConfiguration.get().setAdminAddress("tom.aaaa@gmail.com"); | ||
Mailer.descriptor().setReplyToAddress("tom.aaaa@gmail.com"); | ||
JenkinsLocationConfiguration.get().setAdminAddress(A); | ||
Mailer.descriptor().setReplyToAddress(A); | ||
} | ||
|
||
@Test | ||
|
@@ -72,18 +80,18 @@ public void test_construction() throws Exception { | |
Address[] from = mimeMessage.getFrom(); | ||
Assert.assertNotNull(from); | ||
Assert.assertEquals(1, from.length); | ||
Assert.assertEquals("tom.aaaa@gmail.com", from[0].toString()); | ||
Assert.assertEquals(A, from[0].toString()); | ||
Address[] replyTo = mimeMessage.getReplyTo(); | ||
Assert.assertNotNull(from); | ||
Assert.assertEquals(1, replyTo.length); | ||
Assert.assertEquals("tom.aaaa@gmail.com", replyTo[0].toString()); | ||
Assert.assertEquals(A, replyTo[0].toString()); | ||
|
||
// check the recipient list... | ||
Address[] allRecipients = mimeMessage.getAllRecipients(); | ||
Assert.assertNotNull(allRecipients); | ||
Assert.assertEquals(2, allRecipients.length); | ||
Assert.assertEquals("tom.xxxx@gmail.com", allRecipients[0].toString()); | ||
Assert.assertEquals("tom.yyyy@gmail.com", allRecipients[1].toString()); | ||
Assert.assertEquals(X, allRecipients[0].toString()); | ||
Assert.assertEquals(Y, allRecipients[1].toString()); | ||
|
||
// Make sure we can regen the instance identifier public key | ||
String encodedIdent = mimeMessage.getHeader("X-Instance-Identity")[0]; | ||
|
@@ -164,4 +172,28 @@ public void test_addRecipients_tokenizer() throws Exception { | |
Assert.assertEquals("tom.yyyy@gmail.com", recipients[1].toString()); | ||
Assert.assertEquals("tom.zzzz@gmail.com", recipients[2].toString()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could use constants everywhere since you are doing it in other places, just to be consistent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to avoid "unrelated changes", will do a further clean up in another PR. |
||
} | ||
|
||
@Test | ||
@Issue("JENKINS-32301") | ||
public void testMultipleReplyToAddress() throws Exception { | ||
checkMultipleReplyToAddress("%s %s %s"); | ||
checkMultipleReplyToAddress("%s , %s %s"); | ||
checkMultipleReplyToAddress("%s %s, %s"); | ||
checkMultipleReplyToAddress("%s,%s,%s"); | ||
checkMultipleReplyToAddress(new MimeMessageBuilder().setReplyTo(X).addReplyTo(Y).addReplyTo(Z)); | ||
} | ||
|
||
private void checkMultipleReplyToAddress(String replyTo) throws Exception { | ||
checkMultipleReplyToAddress(new MimeMessageBuilder().setReplyTo(String.format(replyTo, X, Y, Z))); | ||
} | ||
|
||
private void checkMultipleReplyToAddress(MimeMessageBuilder messageBuilder) throws Exception { | ||
MimeMessage mimeMessage = messageBuilder.buildMimeMessage(); | ||
Address[] recipients = mimeMessage.getReplyTo(); | ||
Assert.assertEquals(3, recipients.length); | ||
Assert.assertEquals(X, recipients[0].toString()); | ||
Assert.assertEquals(Y, recipients[1].toString()); | ||
Assert.assertEquals(Z, recipients[2].toString()); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MimeMessageBuilder is single-thread, right? If no, the new code introduces concurrency issues