| author | akozak | 2011-11-25 02:16:14 (EST) |
|---|---|---|
| committer | Winston Prakash | 2011-12-01 20:47:32 (EST) |
| commit | 1bd14830a3262f92002654e0d05cd95d0501eeec (patch) (side-by-side diff) | |
| tree | c94939824c4481f2f3e42067423e071b7f252939 | |
| parent | a360fdb020921bd503e4e31b66557875d54c72d6 (diff) | |
| download | org.eclipse.hudson.core-1bd14830a3262f92002654e0d05cd95d0501eeec.zip org.eclipse.hudson.core-1bd14830a3262f92002654e0d05cd95d0501eeec.tar.gz org.eclipse.hudson.core-1bd14830a3262f92002654e0d05cd95d0501eeec.tar.bz2 | |
FIxed HUDSON-8938 (Option to notify user of Hudson account creation Refactored MailSender in order to simplify the logic Improved mail templates.
Signed-off-by: Winston Prakash <winston.prakash@gmail.com>
11 files changed, 856 insertions, 326 deletions
diff --git a/hudson-core/src/main/java/hudson/mail/BaseMailSender.java b/hudson-core/src/main/java/hudson/mail/BaseMailSender.java new file mode 100644 index 0000000..4e35043 --- a/dev/null +++ b/hudson-core/src/main/java/hudson/mail/BaseMailSender.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * + * Copyright (c) 2011 Oracle Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * + * Anton Kozak + * + *******************************************************************************/ +package hudson.mail; + +import hudson.tasks.HudsonMimeMessage; +import hudson.tasks.Mailer; +import java.util.Date; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.StringTokenizer; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +/** + * Base logic of sending out notification e-mail. + */ +public abstract class BaseMailSender { + private static final String DEFAULT_TEXT = "Should be overridden"; + + protected static final String DEFAULT_CHARSET = "UTF-8"; + /** + * Whitespace-separated list of e-mail addresses that represent recipients. + */ + private String recipients; + + /** + * The charset to use for the text and subject. + */ + private String charset; + + + public BaseMailSender(String recipients) { + this(recipients, DEFAULT_CHARSET); + } + + public BaseMailSender(String recipients, String charset) { + this.recipients = recipients; + this.charset = charset; + } + + /** + * Returns recipients. + * + * @return recipients. + */ + public String getRecipients() { + return recipients; + } + + /** + * Returns charset. + * + * @return charset. + */ + public String getCharset() { + return charset; + } + + public boolean execute() { + try { + MimeMessage mail = getMail(); + if (mail != null) { + if (mail.getAllRecipients() != null) { + Mailer.descriptor().send((HudsonMimeMessage) mail); + } + } + } catch (MessagingException ignore) { + //TODO add logging + } + return true; + } + + /** + * Returns prepared email. + * + * @return prepared email. + * @throws MessagingException exception if any. + */ + protected MimeMessage getMail() throws MessagingException { + MimeMessage msg = new HudsonMimeMessage(Mailer.descriptor().createSession()); + msg.setContent("", "text/plain"); + msg.setFrom(new InternetAddress(Mailer.descriptor().getAdminAddress())); + msg.setSentDate(new Date()); + + Set<InternetAddress> rcp = new LinkedHashSet<InternetAddress>(); + StringTokenizer tokens = new StringTokenizer(recipients); + while (tokens.hasMoreTokens()) { + String address = tokens.nextToken(); + try { + rcp.add(new InternetAddress(address)); + } catch (AddressException ignore) { + // ignore bad address, but try to send to other addresses + } + } + msg.setRecipients(Message.RecipientType.TO, rcp.toArray(new InternetAddress[rcp.size()])); + msg.setSubject(new StringBuilder().append(getSubjectPrefix()).append(" ").append(getSubject()).toString(), + charset); + msg.setText(new StringBuilder().append(getText()).append(getTextFooter()).toString(), charset); + return msg; + } + + /** + * Returns the text of the email. + * + * @return the text of the email. + */ + protected String getText() { + return DEFAULT_TEXT; + } + + /** + * Returns the subject of the email. + * + * @return the subject of the email. + */ + protected String getSubject() { + return DEFAULT_TEXT; + } + + /** + * Returns text footer for all automatically generated emails. + * + * @return text footer. + */ + protected String getTextFooter() { + return hudson.mail.Messages.hudson_email_footer(); + } + + /** + * Returns prefix for subject of automatically generated emails. + * + * @return prefix for subject. + */ + protected String getSubjectPrefix() { + return hudson.mail.Messages.hudson_email_subject_prefix(); + } + +} diff --git a/hudson-core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/hudson-core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java index dbfb67a..85b471c 100644 --- a/hudson-core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java +++ b/hudson-core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java @@ -16,19 +16,21 @@ package hudson.security; -import hudson.security.captcha.CaptchaSupport; +import hudson.mail.BaseMailSender; import com.thoughtworks.xstream.converters.UnmarshallingContext; import hudson.Extension; import hudson.Util; import hudson.diagnosis.OldDataMonitor; import hudson.model.*; import hudson.security.FederatedLoginService.FederatedIdentity; +import hudson.security.captcha.CaptchaSupport; import hudson.tasks.Mailer; import hudson.util.PluginServletFilter; import hudson.util.Protector; import hudson.util.Scrambler; import hudson.util.XStream2; import net.sf.json.JSONObject; +import org.apache.commons.lang3.StringUtils; import org.springframework.security.Authentication; import org.springframework.security.AuthenticationException; import org.springframework.security.BadCredentialsException; @@ -74,21 +76,34 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea * If true, captcha will be enabled. */ private final boolean enableCaptcha; - - + + /** + * If true, user will be notified of Hudson account creation. + */ + private final boolean notifyUser; + /** * @deprecated as of 2.0.1 */ @Deprecated public HudsonPrivateSecurityRealm(boolean allowsSignup) { - this(allowsSignup, true, null); + this(allowsSignup, true); + } + + /** + * @deprecated as of 2.2.0 + */ + @Deprecated + public HudsonPrivateSecurityRealm(boolean allowsSignup, boolean enableCaptcha) { + this(allowsSignup, true, null, false); } @DataBoundConstructor - public HudsonPrivateSecurityRealm(boolean allowsSignup, boolean enableCaptcha, CaptchaSupport captchaSupport) { + public HudsonPrivateSecurityRealm(boolean allowsSignup, boolean enableCaptcha, CaptchaSupport captchaSupport, boolean notifyUser) { this.disableSignup = !allowsSignup; this.enableCaptcha = enableCaptcha; setCaptchaSupport(captchaSupport); + this.notifyUser = notifyUser; if(!allowsSignup && !hasSomeUser()) { // if Hudson is newly set up with the security realm and there's no user account created yet, @@ -114,7 +129,16 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea public boolean isEnableCaptcha() { return enableCaptcha; } - + + /** + * Returns true if Hudson should notify user of account creation. + * + * @return true if Hudson should notify user of account creation. + */ + public boolean isNotifyUser() { + return notifyUser; + } + /** * Computes if this Hudson has some user accounts configured. * @@ -138,7 +162,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea @Override public Details loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { - User u = User.get(username,false); + User u = User.get(username, false); Details p = u!=null ? u.getProperty(Details.class) : null; if(p==null) throw new UsernameNotFoundException("Password is not set: "+username); @@ -258,7 +282,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea AuthorizationStrategy as = Hudson.getInstance().getAuthorizationStrategy(); if (as instanceof GlobalMatrixAuthorizationStrategy) { GlobalMatrixAuthorizationStrategy ma = (GlobalMatrixAuthorizationStrategy) as; - ma.add(Hudson.ADMINISTER,u.getId()); + ma.add(Hudson.ADMINISTER, u.getId()); } } @@ -303,13 +327,33 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea } // register the user - User user = createAccount(si.username,si.password1); + final User user = createAccount(si.username,si.password1); user.addProperty(new Mailer.UserProperty(si.email)); user.setFullName(si.fullname); user.save(); + if (notifyUser && StringUtils.isNotEmpty(si.email)) { + notifyUser(si.username, si.email, si.fullname, si.password1); + } return user; } + private void notifyUser(final String username, final String email, final String fullname, final String passwd) { + new BaseMailSender(email) { + @Override + protected String getText() { + String baseUrl = Mailer.descriptor().getUrl(); + return hudson.mail.Messages + .account_creation_email_text(fullname != null ? fullname : "", baseUrl, email, username, + passwd); + } + + @Override + protected String getSubject() { + return hudson.mail.Messages.account_creation_email_subject(); + } + }.execute(); + } + /** * Creates a new user account by registering a password to the user. */ @@ -550,7 +594,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea public static final class ManageUserLinks extends ManagementLink { public String getIconFileName() { if(Hudson.getInstance().getSecurityRealm() instanceof HudsonPrivateSecurityRealm) - return "user.png"; + return "user.gif"; else return null; // not applicable now } @@ -578,7 +622,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea * * <p> * This abbreviates the need to store the salt separately, which in turn allows us to hide the salt handling - * in this little class. The rest of the Spring Security thinks that we are not using salt. + * in this little class. The rest of the Acegi thinks that we are not using salt. */ public static final PasswordEncoder PASSWORD_ENCODER = new PasswordEncoder() { private final PasswordEncoder passwordEncoder = new ShaPasswordEncoder(256); diff --git a/hudson-core/src/main/java/hudson/tasks/MailSender.java b/hudson-core/src/main/java/hudson/tasks/MailSender.java index 0d583c0..3458df1 100644 --- a/hudson-core/src/main/java/hudson/tasks/MailSender.java +++ b/hudson-core/src/main/java/hudson/tasks/MailSender.java @@ -1,6 +1,6 @@ /******************************************************************************* * - * Copyright (c) 2004-2010 Oracle Corporation. + * Copyright (c) 2004-2011 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -9,42 +9,28 @@ * * Contributors: * - * Kohsuke Kawaguchi, Bruce Chapman, Daniel Dyer, Jean-Baptiste Quenot + * Kohsuke Kawaguchi, Bruce Chapman, Daniel Dyer, Jean-Baptiste Quenot, Anton Kozak * * *******************************************************************************/ package hudson.tasks; -import hudson.FilePath; -import hudson.Functions; -import hudson.Util; +import hudson.mail.BaseMailSender; +import hudson.tasks.mail.impl.BackToNormalBuildMail; +import hudson.tasks.mail.impl.FailureBuildMail; +import hudson.tasks.mail.impl.UnstableBuildMail; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.CheckPoint; -import hudson.model.Hudson; import hudson.model.Result; -import hudson.model.User; -import hudson.scm.ChangeLogSet; -import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.mail.Address; -import javax.mail.Message; import javax.mail.MessagingException; -import javax.mail.internet.AddressException; -import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; /** @@ -53,13 +39,9 @@ import javax.mail.internet.MimeMessage; * @author Jesse Glick * @author Kohsuke Kawaguchi */ -public class MailSender { - /** - * Whitespace-separated list of e-mail addresses that represent recipients. - */ - private String recipients; - - private List<AbstractProject> includeUpstreamCommitters = new ArrayList<AbstractProject>(); +public class MailSender extends BaseMailSender { + + private List<AbstractProject> upstreamProjects = new ArrayList<AbstractProject>(); /** * If true, only the first unstable build will be reported. @@ -70,27 +52,24 @@ public class MailSender { * If true, individuals will receive e-mails regarding who broke the build. */ private boolean sendToIndividuals; - - /** - * The charset to use for the text and subject. - */ - private String charset; public MailSender(String recipients, boolean dontNotifyEveryUnstableBuild, boolean sendToIndividuals) { - this(recipients, dontNotifyEveryUnstableBuild, sendToIndividuals, "UTF-8"); + this(recipients, dontNotifyEveryUnstableBuild, sendToIndividuals, DEFAULT_CHARSET); } - public MailSender(String recipients, boolean dontNotifyEveryUnstableBuild, boolean sendToIndividuals, String charset) { - this(recipients,dontNotifyEveryUnstableBuild,sendToIndividuals,charset, Collections.<AbstractProject>emptyList()); + public MailSender(String recipients, boolean dontNotifyEveryUnstableBuild, boolean sendToIndividuals, + String charset) { + this(recipients, dontNotifyEveryUnstableBuild, sendToIndividuals, charset, + Collections.<AbstractProject>emptyList()); } - - public MailSender(String recipients, boolean dontNotifyEveryUnstableBuild, boolean sendToIndividuals, String charset, Collection<AbstractProject> includeUpstreamCommitters) { - this.recipients = recipients; + + public MailSender(String recipients, boolean dontNotifyEveryUnstableBuild, boolean sendToIndividuals, + String charset, Collection<AbstractProject> includeUpstreamCommitters) { + super(recipients, charset); this.dontNotifyEveryUnstableBuild = dontNotifyEveryUnstableBuild; this.sendToIndividuals = sendToIndividuals; - this.charset = charset; - this.includeUpstreamCommitters.addAll(includeUpstreamCommitters); + this.upstreamProjects.addAll(includeUpstreamCommitters); } public boolean execute(AbstractBuild<?, ?> build, BuildListener listener) throws InterruptedException { @@ -147,271 +126,41 @@ public class MailSender { protected MimeMessage getMail(AbstractBuild<?, ?> build, BuildListener listener) throws MessagingException, InterruptedException { if (build.getResult() == Result.FAILURE) { - return createFailureMail(build, listener); + return new FailureBuildMail(getRecipients(), sendToIndividuals, upstreamProjects, getCharset()). + getMail(build, listener); } if (build.getResult() == Result.UNSTABLE) { if (!dontNotifyEveryUnstableBuild) - return createUnstableMail(build, listener); + return new UnstableBuildMail(getRecipients(), sendToIndividuals, upstreamProjects, getCharset()). + getMail(build, listener); Result prev = findPreviousBuildResult(build); if (prev == Result.SUCCESS) - return createUnstableMail(build, listener); + return new UnstableBuildMail(getRecipients(), sendToIndividuals, upstreamProjects, getCharset()). + getMail(build, listener); } if (build.getResult() == Result.SUCCESS) { Result prev = findPreviousBuildResult(build); if (prev == Result.FAILURE) - return createBackToNormalMail(build, Messages.MailSender_BackToNormal_Normal(), listener); + return new BackToNormalBuildMail(getRecipients(), sendToIndividuals, upstreamProjects, getCharset(), + Messages.MailSender_BackToNormal_Normal()).getMail(build, listener); if (prev == Result.UNSTABLE) - return createBackToNormalMail(build, Messages.MailSender_BackToNormal_Stable(), listener); + return new BackToNormalBuildMail(getRecipients(), sendToIndividuals, upstreamProjects, getCharset(), + Messages.MailSender_BackToNormal_Stable()).getMail(build, listener); } return null; } - private MimeMessage createBackToNormalMail(AbstractBuild<?, ?> build, String subject, BuildListener listener) throws MessagingException { - MimeMessage msg = createEmptyMail(build, listener); - - msg.setSubject(getSubject(build, Messages.MailSender_BackToNormalMail_Subject(subject)),charset); - StringBuilder buf = new StringBuilder(); - appendBuildUrl(build, buf); - msg.setText(buf.toString(),charset); - - return msg; - } - - private MimeMessage createUnstableMail(AbstractBuild<?, ?> build, BuildListener listener) throws MessagingException { - MimeMessage msg = createEmptyMail(build, listener); - - String subject = Messages.MailSender_UnstableMail_Subject(); - - AbstractBuild<?, ?> prev = build.getPreviousBuild(); - boolean still = false; - if(prev!=null) { - if(prev.getResult()==Result.SUCCESS) - subject =Messages.MailSender_UnstableMail_ToUnStable_Subject(); - else if(prev.getResult()==Result.UNSTABLE) { - subject = Messages.MailSender_UnstableMail_StillUnstable_Subject(); - still = true; - } - } - - msg.setSubject(getSubject(build, subject),charset); - StringBuilder buf = new StringBuilder(); - // Link to project changes summary for "still unstable" if this or last build has changes - if (still && !(build.getChangeSet().isEmptySet() && prev.getChangeSet().isEmptySet())) - appendUrl(Util.encode(build.getProject().getUrl()) + "changes", buf); - else - appendBuildUrl(build, buf); - msg.setText(buf.toString(), charset); - - return msg; - } - - private void appendBuildUrl(AbstractBuild<?, ?> build, StringBuilder buf) { - appendUrl(Util.encode(build.getUrl()) - + (build.getChangeSet().isEmptySet() ? "" : "changes"), buf); - } - - private void appendUrl(String url, StringBuilder buf) { - String baseUrl = Mailer.descriptor().getUrl(); - if (baseUrl != null) - buf.append(Messages.MailSender_Link(baseUrl, url)).append("\n\n"); - } - - private MimeMessage createFailureMail(AbstractBuild<?, ?> build, BuildListener listener) throws MessagingException, InterruptedException { - MimeMessage msg = createEmptyMail(build, listener); - - msg.setSubject(getSubject(build, Messages.MailSender_FailureMail_Subject()),charset); - - StringBuilder buf = new StringBuilder(); - appendBuildUrl(build, buf); - - boolean firstChange = true; - for (ChangeLogSet.Entry entry : build.getChangeSet()) { - if (firstChange) { - firstChange = false; - buf.append(Messages.MailSender_FailureMail_Changes()).append("\n\n"); - } - buf.append('['); - buf.append(entry.getAuthor().getFullName()); - buf.append("] "); - String m = entry.getMsg(); - if (m!=null) { - buf.append(m); - if (!m.endsWith("\n")) { - buf.append('\n'); - } - } - buf.append('\n'); - } - - buf.append("------------------------------------------\n"); - - try { - // Restrict max log size to avoid sending enormous logs over email. - // Interested users can always look at the log on the web server. - List<String> lines = build.getLog(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.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("(" + - Pattern.quote(ws.getRemote()) + "|" + Pattern.quote(ws.toURI().toString()) + ")[/\\\\]?([^:#\\s]*)"); - } - for (String line : lines) { - line = line.replace('\0',' '); // shall we replace other control code? This one is motivated by http://www.nabble.com/Problems-with-NULL-characters-in-generated-output-td25005177.html - 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; - 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 - buf.append(Messages.MailSender_FailureMail_FailedToAccessBuildLog()).append("\n\n").append(Functions.printThrowable(e)); - } - - msg.setText(buf.toString(),charset); - - return msg; - } - - private MimeMessage createEmptyMail(AbstractBuild<?, ?> build, BuildListener listener) throws MessagingException { - MimeMessage msg = new HudsonMimeMessage(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<InternetAddress> rcp = new LinkedHashSet<InternetAddress>(); - StringTokenizer tokens = new StringTokenizer(recipients); - while (tokens.hasMoreTokens()) { - String address = tokens.nextToken(); - if(address.startsWith("upstream-individuals:")) { - // people who made a change in the upstream - String projectName = address.substring("upstream-individuals:".length()); - AbstractProject up = Hudson.getInstance().getItemByFullName(projectName,AbstractProject.class); - if(up==null) { - listener.getLogger().println("No such project exist: "+projectName); - continue; - } - includeCulpritsOf(up, build, listener, rcp); - } else { - // ordinary address - try { - rcp.add(new InternetAddress(address)); - } catch (AddressException e) { - // report bad address, but try to send to other addresses - e.printStackTrace(listener.error(e.getMessage())); - } - } - } - - for (AbstractProject project : includeUpstreamCommitters) { - includeCulpritsOf(project, build, listener, rcp); - } - - if (sendToIndividuals) { - Set<User> culprits = build.getCulprits(); - - if(debug) - listener.getLogger().println("Trying to send e-mails to individuals who broke the build. sizeof(culprits)=="+culprits.size()); - - rcp.addAll(buildCulpritList(listener,culprits)); - } - msg.setRecipients(Message.RecipientType.TO, rcp.toArray(new InternetAddress[rcp.size()])); - - AbstractBuild<?, ?> pb = build.getPreviousBuild(); - if(pb!=null) { - MailMessageIdAction b = pb.getAction(MailMessageIdAction.class); - if(b!=null) { - msg.setHeader("In-Reply-To",b.messageId); - msg.setHeader("References",b.messageId); - } - } - - return msg; - } - - private void includeCulpritsOf(AbstractProject upstreamProject, AbstractBuild<?, ?> currentBuild, BuildListener listener, Set<InternetAddress> recipientList) throws AddressException { - AbstractBuild<?,?> upstreamBuild = currentBuild.getUpstreamRelationshipBuild(upstreamProject); - AbstractBuild<?,?> previousBuild = currentBuild.getPreviousBuild(); - AbstractBuild<?,?> previousBuildUpstreamBuild = previousBuild!=null ? previousBuild.getUpstreamRelationshipBuild(upstreamProject) : null; - if(previousBuild==null && upstreamBuild==null && previousBuildUpstreamBuild==null) { - listener.getLogger().println("Unable to compute the changesets in "+ upstreamProject +". Is the fingerprint configured?"); - return; - } - if(previousBuild==null || upstreamBuild==null || previousBuildUpstreamBuild==null) { - listener.getLogger().println("Unable to compute the changesets in "+ upstreamProject); - return; - } - AbstractBuild<?,?> b=previousBuildUpstreamBuild; - do { - recipientList.addAll(buildCulpritList(listener,b.getCulprits())); - b = b.getNextBuild(); - } while ( b != upstreamBuild && b != null ); - } - - private Set<InternetAddress> buildCulpritList(BuildListener listener, Set<User> culprits) throws AddressException { - Set<InternetAddress> r = new HashSet<InternetAddress>(); - for (User a : culprits) { - String adrs = Util.fixEmpty(a.getProperty(Mailer.UserProperty.class).getAddress()); - if(debug) - listener.getLogger().println(" User "+a.getId()+" -> "+adrs); - if (adrs != null) - r.add(new InternetAddress(adrs)); - else { - listener.getLogger().println(Messages.MailSender_NoAddress(a.getFullName())); - } - } - return r; - } - - private String getSubject(AbstractBuild<?, ?> build, String caption) { - return caption + ' ' + build.getFullDisplayName(); - } - /** * Check whether a path (/-separated) will be archived. */ + //TODO investigate where it's used. protected boolean artifactMatches(String path, AbstractBuild<?, ?> build) { return false; } - public static boolean debug = false; - - private static final int MAX_LOG_LINES = Integer.getInteger(MailSender.class.getName()+".maxLogLines",250); - - /** * Sometimes the outcome of the previous build affects the e-mail we send, hence this checkpoint. */ diff --git a/hudson-core/src/main/java/hudson/tasks/Mailer.java b/hudson-core/src/main/java/hudson/tasks/Mailer.java index e198a9a..3e36a3c 100644 --- a/hudson-core/src/main/java/hudson/tasks/Mailer.java +++ b/hudson-core/src/main/java/hudson/tasks/Mailer.java @@ -1,6 +1,6 @@ /******************************************************************************* * - * Copyright (c) 2004-2010 Oracle Corporation. + * Copyright (c) 2004-2011 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -9,7 +9,7 @@ * * Contributors: * - * Kohsuke Kawaguchi, Bruce Chapman, Erik Ramfelt, Jean-Baptiste Quenot, Luca Domenico Milanesio + * Kohsuke Kawaguchi, Bruce Chapman, Erik Ramfelt, Jean-Baptiste Quenot, Luca Domenico Milanesio, Anton Kozak * * *******************************************************************************/ @@ -33,13 +33,11 @@ import hudson.model.UserPropertyDescriptor; import hudson.util.FormValidation; import hudson.util.Secret; import hudson.util.XStream2; -import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Date; import java.util.Properties; -import java.util.logging.Level; import java.util.logging.Logger; import javax.mail.Authenticator; import javax.mail.Message; @@ -53,7 +51,6 @@ import javax.mail.internet.MimeMessage; import javax.servlet.ServletException; import net.sf.json.JSONObject; import org.apache.commons.lang3.StringUtils; -import org.apache.tools.ant.types.selectors.SelectorUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.QueryParameter; @@ -108,37 +105,17 @@ public class Mailer extends Notifier { private transient String charset; @Override - public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { - if(debug) + public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) + throws IOException, InterruptedException { + if (debug) { listener.getLogger().println("Running mailer"); + } // substitute build parameters EnvVars env = build.getEnvironment(listener); String recip = env.expand(recipients); - return new MailSender(recip, dontNotifyEveryUnstableBuild, sendToIndividuals, descriptor().getCharset()) { - /** Check whether a path (/-separated) will be archived. */ - @Override - public boolean artifactMatches(String path, AbstractBuild<?,?> build) { - ArtifactArchiver aa = build.getProject().getPublishersList().get(ArtifactArchiver.class); - if (aa == null) { - LOGGER.finer("No ArtifactArchiver found"); - return false; - } - String artifacts = aa.getArtifacts(); - for (String include : artifacts.split("[, ]+")) { - String pattern = include.replace(File.separatorChar, '/'); - if (pattern.endsWith("/")) { - pattern += "**"; - } - if (SelectorUtils.matchPath(pattern, path)) { - LOGGER.log(Level.FINER, "DescriptorImpl.artifactMatches true for {0} against {1}", new Object[] {path, pattern}); - return true; - } - } - LOGGER.log(Level.FINER, "DescriptorImpl.artifactMatches for {0} matched none of {1}", new Object[] {path, artifacts}); - return false; - } - }.execute(build,listener); + return new MailSender(recip, dontNotifyEveryUnstableBuild, sendToIndividuals, + descriptor().getCharset()).execute(build, listener); } /** diff --git a/hudson-core/src/main/java/hudson/tasks/mail/BuildResultMail.java b/hudson-core/src/main/java/hudson/tasks/mail/BuildResultMail.java new file mode 100644 index 0000000..b0cbfd0 --- a/dev/null +++ b/hudson-core/src/main/java/hudson/tasks/mail/BuildResultMail.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * + * Copyright (c) 2011 Oracle Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * + * Anton Kozak + * + *******************************************************************************/ +package hudson.tasks.mail; + +import hudson.model.AbstractBuild; +import hudson.model.BuildListener; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +/** + * Interface describes mail with project build result. + */ +public interface BuildResultMail { + + /** + * Returns prepared mail. + * + * @param build build. + * @param listener listener. + * + * @return prepared mail. + * @throws MessagingException exception if any. + * @throws InterruptedException exception if any. + */ + MimeMessage getMail(AbstractBuild<?, ?> build, BuildListener listener) + throws MessagingException, InterruptedException; + +}
\ No newline at end of file diff --git a/hudson-core/src/main/java/hudson/tasks/mail/impl/BackToNormalBuildMail.java b/hudson-core/src/main/java/hudson/tasks/mail/impl/BackToNormalBuildMail.java new file mode 100644 index 0000000..bd15df3 --- a/dev/null +++ b/hudson-core/src/main/java/hudson/tasks/mail/impl/BackToNormalBuildMail.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * + * Copyright (c) 2011 Oracle Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * + * Anton Kozak + * + *******************************************************************************/ +package hudson.tasks.mail.impl; + +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.tasks.Messages; +import java.util.List; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +/** + * Class used for the mail preparation if build was returned to normal state. + */ +public class BackToNormalBuildMail extends BaseBuildResultMail { + + /** + * Current state. + */ + private String currentState; + + public BackToNormalBuildMail(String recipients, boolean sendToIndividuals, + List<AbstractProject> upstreamProjects, String charset, String currentState) { + super(recipients, sendToIndividuals, upstreamProjects, charset); + this.currentState = currentState; + } + + /** + * @inheritDoc + */ + public MimeMessage getMail(AbstractBuild<?, ?> build, BuildListener listener) + throws MessagingException, InterruptedException { + MimeMessage msg = createEmptyMail(build, listener); + msg.setSubject(getSubject(build, Messages.MailSender_BackToNormalMail_Subject(currentState)), getCharset()); + StringBuilder buf = new StringBuilder(); + appendBuildUrl(build, buf); + appendFooter(buf); + msg.setText(buf.toString(), getCharset()); + return msg; + } +}
\ No newline at end of file diff --git a/hudson-core/src/main/java/hudson/tasks/mail/impl/BaseBuildResultMail.java b/hudson-core/src/main/java/hudson/tasks/mail/impl/BaseBuildResultMail.java new file mode 100644 index 0000000..c3a5127 --- a/dev/null +++ b/hudson-core/src/main/java/hudson/tasks/mail/impl/BaseBuildResultMail.java @@ -0,0 +1,271 @@ +/******************************************************************************* + * + * Copyright (c) 2011 Oracle Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * + * Anton Kozak + * + *******************************************************************************/ +package hudson.tasks.mail.impl; + +import hudson.Util; +import hudson.tasks.mail.BuildResultMail; +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.model.Hudson; +import hudson.model.User; +import hudson.tasks.HudsonMimeMessage; +import hudson.tasks.MailMessageIdAction; +import hudson.tasks.MailSender; +import hudson.tasks.Mailer; +import hudson.tasks.Messages; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.StringTokenizer; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import org.apache.commons.collections.CollectionUtils; + +/** + * Base class for all project build result mails. + */ +public abstract class BaseBuildResultMail implements BuildResultMail { + + protected static final int MAX_LOG_LINES = Integer.getInteger(MailSender.class.getName()+".maxLogLines",250); + + //TODO where it's used? + public static boolean debug = false; + + /** + * Whitespace-separated list of e-mail addresses that represent recipients. + */ + private String recipients; + + /** + * The charset to use for the text and subject. + */ + private String charset; + + /** + * The list of upstream projects. + */ + private List<AbstractProject> upstreamProjects; + + /** + * If true, individuals will receive e-mails regarding who broke the build. + */ + private boolean sendToIndividuals; + + + public BaseBuildResultMail(String recipients, boolean sendToIndividuals, List<AbstractProject> upstreamProjects, + String charset) { + this.recipients = recipients; + this.sendToIndividuals = sendToIndividuals; + this.upstreamProjects = upstreamProjects; + this.charset = charset; + } + + /** + * Returns recipients. + * + * @return recipients. + */ + public String getRecipients() { + return recipients; + } + + /** + * Returns charset. + * + * @return charset. + */ + public String getCharset() { + return charset; + } + + /** + * Returns prefix for subject of automatically generated emails. + * + * @return prefix for subject. + */ + protected String getSubjectPrefix() { + return hudson.mail.Messages.hudson_email_subject_prefix(); + } + /** + * Creates empty mail. + * + * @param build build. + * @param listener listener. + * @return empty mail. + * @throws MessagingException exception if any. + */ + protected MimeMessage createEmptyMail(AbstractBuild<?, ?> build, BuildListener listener) throws MessagingException { + MimeMessage msg = new HudsonMimeMessage(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<InternetAddress> rcp = new LinkedHashSet<InternetAddress>(); + StringTokenizer tokens = new StringTokenizer(getRecipients()); + while (tokens.hasMoreTokens()) { + String address = tokens.nextToken(); + if(address.startsWith("upstream-individuals:")) { + // people who made a change in the upstream + String projectName = address.substring("upstream-individuals:".length()); + AbstractProject up = Hudson.getInstance().getItemByFullName(projectName,AbstractProject.class); + if(up==null) { + listener.getLogger().println("No such project exist: "+projectName); + continue; + } + includeCulpritsOf(up, build, listener, rcp); + } else { + // ordinary address + try { + rcp.add(new InternetAddress(address)); + } catch (AddressException e) { + // report bad address, but try to send to other addresses + e.printStackTrace(listener.error(e.getMessage())); + } + } + } + + if (CollectionUtils.isNotEmpty(upstreamProjects)) { + for (AbstractProject project : upstreamProjects) { + includeCulpritsOf(project, build, listener, rcp); + } + } + + if (sendToIndividuals) { + Set<User> culprits = build.getCulprits(); + + if(debug) + listener.getLogger().println("Trying to send e-mails to individuals who broke the build. sizeof(culprits)=="+culprits.size()); + + rcp.addAll(buildCulpritList(listener,culprits)); + } + msg.setRecipients(Message.RecipientType.TO, rcp.toArray(new InternetAddress[rcp.size()])); + + AbstractBuild<?, ?> pb = build.getPreviousBuild(); + if(pb!=null) { + MailMessageIdAction b = pb.getAction(MailMessageIdAction.class); + if(b!=null) { + msg.setHeader("In-Reply-To",b.messageId); + msg.setHeader("References",b.messageId); + } + } + + return msg; + } + + /** + * Appends build URL to the builder. + * + * @param build build. + * @param buf {@link StringBuilder}. + */ + protected void appendBuildUrl(AbstractBuild<?, ?> build, StringBuilder buf) { + appendUrl(Util.encode(build.getUrl()) + + (build.getChangeSet().isEmptySet() ? "" : "changes"), buf); + } + + /** + * Appends URL to the builder. + * + * @param url url. + * @param buf {@link StringBuilder}. + */ + protected void appendUrl(String url, StringBuilder buf) { + String baseUrl = Mailer.descriptor().getUrl(); + if (baseUrl != null) + buf.append(Messages.MailSender_Link(baseUrl, url)).append("\n\n"); + } + + /** + * Appends footer to the mail builder. + * + * @param buf {@link StringBuilder}. + */ + protected void appendFooter(StringBuilder buf) { + String footer = getTextFooter(); + if (footer != null) { + buf.append(footer); + } + } + + + /** + * Returns the subject of the mail. + * + * @param build build. + * @param caption the caption. + * @return prepared subject. + */ + protected String getSubject(AbstractBuild<?, ?> build, String caption) { + return new StringBuilder().append(getSubjectPrefix()) + .append(" ") + .append(caption) + .append(" ") + .append(build.getFullDisplayName()) + .toString(); + } + + private void includeCulpritsOf(AbstractProject upstreamProject, AbstractBuild<?, ?> currentBuild, BuildListener listener, Set<InternetAddress> recipientList) throws AddressException { + AbstractBuild<?,?> upstreamBuild = currentBuild.getUpstreamRelationshipBuild(upstreamProject); + AbstractBuild<?,?> previousBuild = currentBuild.getPreviousBuild(); + AbstractBuild<?,?> previousBuildUpstreamBuild = previousBuild!=null ? previousBuild.getUpstreamRelationshipBuild(upstreamProject) : null; + if(previousBuild==null && upstreamBuild==null && previousBuildUpstreamBuild==null) { + listener.getLogger().println("Unable to compute the changesets in "+ upstreamProject +". Is the fingerprint configured?"); + return; + } + if(previousBuild==null || upstreamBuild==null || previousBuildUpstreamBuild==null) { + listener.getLogger().println("Unable to compute the changesets in "+ upstreamProject); + return; + } + AbstractBuild<?,?> b=previousBuildUpstreamBuild; + do { + recipientList.addAll(buildCulpritList(listener,b.getCulprits())); + b = b.getNextBuild(); + } while ( b != upstreamBuild && b != null ); + } + + + private Set<InternetAddress> buildCulpritList(BuildListener listener, Set<User> culprits) throws AddressException { + Set<InternetAddress> r = new HashSet<InternetAddress>(); + for (User a : culprits) { + String adrs = Util.fixEmpty(a.getProperty(Mailer.UserProperty.class).getAddress()); + if(debug) + listener.getLogger().println(" User "+a.getId()+" -> "+adrs); + if (adrs != null) + r.add(new InternetAddress(adrs)); + else { + listener.getLogger().println(Messages.MailSender_NoAddress(a.getFullName())); + } + } + return r; + } + + /** + * Returns text footer for all automatically generated emails. + * + * @return text footer. + */ + private String getTextFooter() { + return hudson.mail.Messages.hudson_email_footer(); + } + +} diff --git a/hudson-core/src/main/java/hudson/tasks/mail/impl/FailureBuildMail.java b/hudson-core/src/main/java/hudson/tasks/mail/impl/FailureBuildMail.java new file mode 100644 index 0000000..e97f8a0 --- a/dev/null +++ b/hudson-core/src/main/java/hudson/tasks/mail/impl/FailureBuildMail.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * + * Copyright (c) 2011 Oracle Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * + * Anton Kozak + * + *******************************************************************************/ +package hudson.tasks.mail.impl; + +import hudson.FilePath; +import hudson.Functions; +import hudson.Util; +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.scm.ChangeLogSet; +import hudson.tasks.ArtifactArchiver; +import hudson.tasks.Mailer; +import hudson.tasks.Messages; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import org.apache.tools.ant.types.selectors.SelectorUtils; + +/** + * Class used for the mail preparation if build was returned to normal state. + */ +public class FailureBuildMail extends BaseBuildResultMail { + + public FailureBuildMail(String recipients, boolean sendToIndividuals, + List<AbstractProject> upstreamProjects, String charset) { + super(recipients, sendToIndividuals, upstreamProjects, charset); + } + + /** + * @inheritDoc + */ + public MimeMessage getMail(AbstractBuild<?, ?> build, BuildListener listener) + throws MessagingException, InterruptedException { + MimeMessage msg = createEmptyMail(build, listener); + + msg.setSubject(getSubject(build, Messages.MailSender_FailureMail_Subject()), getCharset()); + + StringBuilder buf = new StringBuilder(); + appendBuildUrl(build, buf); + + boolean firstChange = true; + for (ChangeLogSet.Entry entry : build.getChangeSet()) { + if (firstChange) { + firstChange = false; + buf.append(Messages.MailSender_FailureMail_Changes()).append("\n\n"); + } + buf.append('['); + buf.append(entry.getAuthor().getFullName()); + buf.append("] "); + String m = entry.getMsg(); + if (m != null) { + buf.append(m); + if (!m.endsWith("\n")) { + buf.append('\n'); + } + } + buf.append('\n'); + } + + buf.append("------------------------------------------\n"); + + try { + // Restrict max log size to avoid sending enormous logs over email. + // Interested users can always look at the log on the web server. + List<String> lines = build.getLog(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.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("(" + + Pattern.quote(ws.getRemote()) + "|" + Pattern.quote(ws.toURI().toString()) + + ")[/\\\\]?([^:#\\s]*)"); + } + for (String line : lines) { + line = line.replace('\0', + ' '); // shall we replace other control code? This one is motivated by http://www.nabble.com/Problems-with-NULL-characters-in-generated-output-td25005177.html + 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; + 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 + buf.append(Messages.MailSender_FailureMail_FailedToAccessBuildLog()).append("\n\n").append( + Functions.printThrowable(e)); + } + appendFooter(buf); + msg.setText(buf.toString(), getCharset()); + + return msg; + } + + /** + * Check whether a path (/-separated) will be archived. + */ + public boolean artifactMatches(String path, AbstractBuild<?, ?> build) { + ArtifactArchiver aa = build.getProject().getPublishersList().get(ArtifactArchiver.class); + if (aa == null) { + //LOGGER.finer("No ArtifactArchiver found"); + return false; + } + String artifacts = aa.getArtifacts(); + for (String include : artifacts.split("[, ]+")) { + String pattern = include.replace(File.separatorChar, '/'); + if (pattern.endsWith("/")) { + pattern += "**"; + } + if (SelectorUtils.matchPath(pattern, path)) { + //LOGGER.log(Level.FINER, "DescriptorImpl.artifactMatches true for {0} against {1}", + // new Object[]{path, pattern}); + return true; + } + } + //LOGGER.log(Level.FINER, "DescriptorImpl.artifactMatches for {0} matched none of {1}", + // new Object[]{path, artifacts}); + return false; + } + +} diff --git a/hudson-core/src/main/java/hudson/tasks/mail/impl/UnstableBuildMail.java b/hudson-core/src/main/java/hudson/tasks/mail/impl/UnstableBuildMail.java new file mode 100644 index 0000000..554d8ce --- a/dev/null +++ b/hudson-core/src/main/java/hudson/tasks/mail/impl/UnstableBuildMail.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * + * Copyright (c) 2011 Oracle Corporation. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * + * Anton Kozak + * + *******************************************************************************/ +package hudson.tasks.mail.impl; + +import hudson.Util; +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.model.Result; +import hudson.tasks.Messages; +import java.util.List; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +public class UnstableBuildMail extends BaseBuildResultMail { + + public UnstableBuildMail(String recipients, boolean sendToIndividuals, + List<AbstractProject> upstreamProjects, String charset) { + super(recipients, sendToIndividuals, upstreamProjects, charset); + } + + /** + * @inheritDoc + */ + public MimeMessage getMail(AbstractBuild<?, ?> build, BuildListener listener) + throws MessagingException, InterruptedException { + MimeMessage msg = createEmptyMail(build, listener); + + String subject = Messages.MailSender_UnstableMail_Subject(); + + AbstractBuild<?, ?> prev = build.getPreviousBuild(); + boolean still = false; + if (prev != null) { + if (prev.getResult() == Result.SUCCESS) { + subject = Messages.MailSender_UnstableMail_ToUnStable_Subject(); + } else if (prev.getResult() == Result.UNSTABLE) { + subject = Messages.MailSender_UnstableMail_StillUnstable_Subject(); + still = true; + } + } + + msg.setSubject(getSubject(build, subject), getCharset()); + StringBuilder buf = new StringBuilder(); + // Link to project changes summary for "still unstable" if this or last build has changes + if (still && !(build.getChangeSet().isEmptySet() && prev.getChangeSet().isEmptySet())) { + appendUrl(Util.encode(build.getProject().getUrl()) + "changes", buf); + } else { + appendBuildUrl(build, buf); + } + appendFooter(buf); + msg.setText(buf.toString(), getCharset()); + + return msg; + } +} diff --git a/hudson-core/src/main/resources/hudson/mail/Messages.properties b/hudson-core/src/main/resources/hudson/mail/Messages.properties new file mode 100644 index 0000000..9765f8d --- a/dev/null +++ b/hudson-core/src/main/resources/hudson/mail/Messages.properties @@ -0,0 +1,10 @@ +hudson.email.subject.prefix=[Hudson] + +hudson.email.footer=\n--\nThis message is automatically generated by Hudson. \n\ +For more information on Hudson, see: http://hudson-ci.org/ + +account.creation.email.text=Dear {0} \n\nYou have signed up for a Hudson account at: {1} \n\ +Here are the details of your account: \n--------------------------------------------------------------------- \ +\n Email: {2}\n Username: {3}\n Password: {4}\n\n\ +You can change the password on the page: {1}securityRealm/user/{3}/configure +account.creation.email.subject=Account signup
\ No newline at end of file diff --git a/hudson-core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/config.jelly b/hudson-core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/config.jelly index 0fa3df7..e0c8e8e 100644 --- a/hudson-core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/config.jelly +++ b/hudson-core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/config.jelly @@ -1,6 +1,6 @@ <!-- ************************************************************************** # -# Copyright (c) 2004-2010 Oracle Corporation. +# Copyright (c) 2004-2011 Oracle Corporation. # # All rights reserved. This program and the accompanying materials # are made available under the terms of the Eclipse Public License v1.0 @@ -20,7 +20,9 @@ <f:checkbox name="privateRealm.allowsSignup" checked="${h.defaultToTrue(instance.allowsSignup())}" title="${%Allow users to sign up}"/> - </f:entry> + <f:checkbox name="privateRealm.notifyUser" checked="${h.defaultToTrue(instance.isNotifyUser())}" + title="${%Notify user of Hudson account creation}"/> + </f:entry> <j:if test="${size(h.captchaSupportDescriptors) gt 0}"> @@ -40,5 +42,4 @@ </j:forEach> </f:dropdownList> </j:if> - </j:jelly> |

