package hudson.tasks; import hudson.FilePath; import hudson.Util; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Result; import hudson.model.User; import hudson.scm.ChangeLogSet; import hudson.scm.ChangeLogSet.Entry; import javax.mail.Address; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.StringTokenizer; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Core logic of sending out notification e-mail. * * @author Jesse Glick * @author Kohsuke Kawaguchi */ public class MailSender
, B extends AbstractBuild
> {
/**
* Whitespace-separated list of e-mail addresses that represent recipients.
*/
private String recipients;
/**
* If true, only the first unstable build will be reported.
*/
private boolean dontNotifyEveryUnstableBuild;
/**
* If true, individuals will receive e-mails regarding who broke the build.
*/
private boolean sendToIndividuals;
public MailSender(String recipients, boolean dontNotifyEveryUnstableBuild, boolean sendToIndividuals) {
this.recipients = recipients;
this.dontNotifyEveryUnstableBuild = dontNotifyEveryUnstableBuild;
this.sendToIndividuals = sendToIndividuals;
}
public boolean execute(B build, BuildListener listener) throws InterruptedException {
try {
MimeMessage mail = getMail(build, listener);
if (mail != null) {
Address[] allRecipients = mail.getAllRecipients();
if (allRecipients != null) {
StringBuffer buf = new StringBuffer("Sending e-mails to:");
for (Address a : allRecipients)
buf.append(' ').append(a);
listener.getLogger().println(buf);
Transport.send(mail);
} else {
listener.getLogger().println("An attempt to send an e-mail"
+ " to empty list of recipients, ignored.");
}
}
} catch (MessagingException e) {
e.printStackTrace(listener.error(e.getMessage()));
}
return true;
}
private MimeMessage getMail(B build, BuildListener listener) throws MessagingException, InterruptedException {
if (build.getResult() == Result.FAILURE) {
return createFailureMail(build, listener);
}
if (build.getResult() == Result.UNSTABLE) {
B prev = build.getPreviousBuild();
if (!dontNotifyEveryUnstableBuild)
return createUnstableMail(build, listener);
if (prev != null) {
if (prev.getResult() == Result.SUCCESS)
return createUnstableMail(build, listener);
}
}
if (build.getResult() == Result.SUCCESS) {
B prev = build.getPreviousBuild();
if (prev != null) {
if (prev.getResult() == Result.FAILURE)
return createBackToNormalMail(build, "normal", listener);
if (prev.getResult() == Result.UNSTABLE)
return createBackToNormalMail(build, "stable", listener);
}
}
return null;
}
private MimeMessage createBackToNormalMail(B build, String subject, BuildListener listener) throws MessagingException {
MimeMessage msg = createEmptyMail(build, listener);
msg.setSubject(getSubject(build, "Hudson build is back to " + subject + ": "));
StringBuffer buf = new StringBuffer();
appendBuildUrl(build, buf);
msg.setText(buf.toString());
return msg;
}
private MimeMessage createUnstableMail(B build, BuildListener listener) throws MessagingException {
MimeMessage msg = createEmptyMail(build, listener);
msg.setSubject(getSubject(build, "Hudson build became unstable: "));
StringBuffer buf = new StringBuffer();
appendBuildUrl(build, buf);
msg.setText(buf.toString());
return msg;
}
private void appendBuildUrl(B build, StringBuffer buf) {
String baseUrl = Mailer.DESCRIPTOR.getUrl();
if (baseUrl != null) {
buf.append("See ").append(baseUrl).append(Util.encode(build.getUrl())).append("changes\n\n");
}
}
private MimeMessage createFailureMail(B build, BuildListener listener) throws MessagingException, InterruptedException {
MimeMessage msg = createEmptyMail(build, listener);
msg.setSubject(getSubject(build, "Build failed in Hudson: "));
StringBuffer buf = new StringBuffer();
appendBuildUrl(build, buf);
boolean firstChange = true;
for (ChangeLogSet.Entry entry : build.getChangeSet()) {
if (firstChange) {
firstChange = false;
buf.append("Changes:\n\n");
}
buf.append('[');
buf.append(entry.getAuthor().getFullName());
buf.append("] ");
String m = entry.getMsg();
buf.append(m);
if (!m.endsWith("\n")) {
buf.append('\n');
}
buf.append('\n');
}
buf.append("------------------------------------------\n");
try {
String log = build.getLog();
String[] lines = log.split("\n");
int start = 0;
if (lines.length > MAX_LOG_LINES) {
// Avoid sending enormous logs over email.
// Interested users can always look at the log on the web server.
buf.append("[...truncated " + (lines.length - MAX_LOG_LINES) + " lines...]\n");
start = lines.length - MAX_LOG_LINES;
}
String workspaceUrl = null, artifactUrl = null;
Pattern wsPattern = null;
String baseUrl = Mailer.DESCRIPTOR.getUrl();
if (baseUrl != null) {
// Hyperlink local file paths to the repository workspace or build artifacts.
// Note that it is possible for a failure mail to refer to a file using a workspace
// URL which has already been corrected in a subsequent build. To fix, archive.
workspaceUrl = baseUrl + Util.encode(build.getProject().getUrl()) + "ws/";
artifactUrl = baseUrl + Util.encode(build.getUrl()) + "artifact/";
FilePath ws = build.getProject().getWorkspace();
// Match either file or URL patterns, i.e. either
// c:\hudson\workdir\jobs\foo\workspace\src\Foo.java
// file:/c:/hudson/workdir/jobs/foo/workspace/src/Foo.java
// will be mapped to one of:
// http://host/hudson/job/foo/ws/src/Foo.java
// http://host/hudson/job/foo/123/artifact/src/Foo.java
// Careful with path separator between $1 and $2:
// workspaceDir will not normally end with one;
// workspaceDir.toURI() will end with '/' if and only if workspaceDir.exists() at time of call
wsPattern = Pattern.compile("(" +
quoteRegexp(ws.getRemote()) + "|" + quoteRegexp(ws.toURI().toString()) + ")[/\\\\]?([^:#\\s]*)");
}
for (int i = start; i < lines.length; i++) {
String line = lines[i];
if (wsPattern != null) {
// Perl: $line =~ s{$rx}{$path = $2; $path =~ s!\\\\!/!g; $workspaceUrl . $path}eg;
Matcher m = wsPattern.matcher(line);
int pos = 0;
while (m.find(pos)) {
String path = m.group(2).replace(File.separatorChar, '/');
String linkUrl = artifactMatches(path, build) ? artifactUrl : workspaceUrl;
// Append ' ' to make sure mail readers do not interpret following ':' as part of URL:
String prefix = line.substring(0, m.start()) + linkUrl + Util.encode(path) + ' ';
pos = prefix.length();
line = prefix + line.substring(m.end());
// XXX better style to reuse Matcher and fix offsets, but more work
m = wsPattern.matcher(line);
}
}
buf.append(line);
buf.append('\n');
}
} catch (IOException e) {
// somehow failed to read the contents of the log
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
buf.append("Failed to access build log\n\n").append(sw);
}
msg.setText(buf.toString());
return msg;
}
private MimeMessage createEmptyMail(B build, BuildListener listener) throws MessagingException {
MimeMessage msg = new MimeMessage(Mailer.DESCRIPTOR.createSession());
// TODO: I'd like to put the URL to the page in here,
// but how do I obtain that?
msg.setContent("", "text/plain");
msg.setFrom(new InternetAddress(Mailer.DESCRIPTOR.getAdminAddress()));
msg.setSentDate(new Date());
Set