diff --git a/pom.xml b/pom.xml index 8723ec5..015e470 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.jvnet.hudson.plugins plugin - 1.319 + 1.345 ../pom.xml @@ -57,5 +57,11 @@ 1.8.2 test + + + org.codehaus.groovy + groovy-all + 1.5.6 + diff --git a/src/main/java/hudson/plugins/emailext/EmailType.java b/src/main/java/hudson/plugins/emailext/EmailType.java index 20f5f94..e5687df 100644 --- a/src/main/java/hudson/plugins/emailext/EmailType.java +++ b/src/main/java/hudson/plugins/emailext/EmailType.java @@ -40,6 +40,11 @@ public class EmailType { */ private boolean sendToRecipientList; + /** + * Specifies whether the mail's content should be interpreted as Groovy script + */ + private boolean script; + public EmailType(){ subject = ""; body = ""; @@ -47,6 +52,7 @@ public class EmailType { sendToDevelopers = false; includeCulprits = false; sendToRecipientList = false; + script = false; } public String getSubject() { @@ -104,4 +110,11 @@ public class EmailType { this.recipientList = recipientList; } + public boolean isScript() { + return script; + } + + public void setScript(boolean script) { + this.script = script; + } } diff --git a/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisher.java b/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisher.java index 51d57ed..1d5e2c2 100644 --- a/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisher.java +++ b/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisher.java @@ -2,12 +2,7 @@ package hudson.plugins.emailext; import hudson.Extension; import hudson.Launcher; -import hudson.model.AbstractBuild; -import hudson.model.AbstractProject; -import hudson.model.BuildListener; -import hudson.model.Hudson; -import hudson.model.Result; -import hudson.model.User; +import hudson.model.*; import hudson.plugins.emailext.plugins.ContentBuilder; import hudson.plugins.emailext.plugins.EmailTrigger; import hudson.plugins.emailext.plugins.EmailTriggerDescriptor; @@ -20,8 +15,7 @@ import hudson.tasks.Notifier; import hudson.tasks.Publisher; import hudson.util.FormValidation; import net.sf.json.JSONObject; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.*; import javax.mail.Address; import javax.mail.Authenticator; @@ -46,6 +40,10 @@ import java.util.Properties; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import java.nio.charset.Charset; + +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; /** * {@link Publisher} that sends notification e-mail. @@ -69,8 +67,10 @@ public class ExtendedEmailPublisher extends Notifier { public static final String PROJECT_DEFAULT_BODY_TEXT = "$PROJECT_DEFAULT_CONTENT"; public static final String CHARSET = "utf-8"; - - public static void addEmailTriggerType(EmailTriggerDescriptor triggerType) throws EmailExtException { + + private static final String DEFAULT_CHARSET_SENTINAL = "default"; + + public static void addEmailTriggerType(EmailTriggerDescriptor triggerType) throws EmailExtException { if(EMAIL_TRIGGER_TYPE_MAP.containsKey(triggerType.getMailerId())) throw new EmailExtException("An email trigger type with name " + triggerType.getTriggerName() + " was already added."); @@ -116,6 +116,11 @@ public class ExtendedEmailPublisher extends Notifier { public String contentType; /** + * The charset of the emails for this project. + */ + public String charset; + + /** * The default subject of the emails for this project. ($PROJECT_DEFAULT_SUBJECT) */ public String defaultSubject; @@ -125,6 +130,10 @@ public class ExtendedEmailPublisher extends Notifier { */ public String defaultContent; + public boolean defaultContentIsScript; + + public String buildForTesting; + /** * Get the list of configured email triggers for this project. */ @@ -262,8 +271,7 @@ public class ExtendedEmailPublisher extends Notifier { msg.setFrom(new InternetAddress(ExtendedEmailPublisher.DESCRIPTOR.getAdminAddress())); } - // Set the contents of the email - + //Set the contents of the email msg.setSentDate(new Date()); setSubject( type, build, msg ); @@ -321,18 +329,37 @@ public class ExtendedEmailPublisher extends Notifier { return msg; } + private static boolean isNullOrBlank(String s) { // or, add dependency on commons-lang StringUtils instead? + return s == null || s.trim().length() == 0; + } + + private String getCharset() { + String cs = charset; + if (isNullOrBlank(cs) || DEFAULT_CHARSET_SENTINAL.equalsIgnoreCase(cs)) { + cs = DESCRIPTOR.getDefaultCharset(); + } + if (isNullOrBlank(cs) || DEFAULT_CHARSET_SENTINAL.equalsIgnoreCase(cs)) { + return CHARSET; + } else { + return cs; + } + } + private void setSubject( final EmailType type, final AbstractBuild build, MimeMessage msg ) throws MessagingException { String subject = new ContentBuilder().transformText(type.getSubject(), this, type, (AbstractBuild)build); - msg.setSubject(subject, CHARSET); + msg.setSubject(subject, getCharset()); } private void setContent( final EmailType type, final AbstractBuild build, MimeMessage msg ) throws MessagingException { final String text = new ContentBuilder().transformText(type.getBody(), this, type, (AbstractBuild)build); + msg.setContent(text, getContentType()); + } + private String getContentType() { String messageContentType = contentType; // contentType is null if the project was not reconfigured after upgrading. if (messageContentType == null || "default".equals(messageContentType)) { @@ -343,9 +370,8 @@ public class ExtendedEmailPublisher extends Notifier { messageContentType = "text/plain"; } } - messageContentType += "; charset=" + CHARSET; - - msg.setContent(text, messageContentType); + messageContentType += "; charset=" + getCharset(); + return messageContentType; } private static void addAddress(List addresses, String address, BuildListener listener) { @@ -423,6 +449,11 @@ public class ExtendedEmailPublisher extends Notifier { private String defaultContentType; /** + * This is a global default charset (mime type) for emails. + */ + private String defaultCharset; + + /** * This is a global default subject line for sending emails. */ private String defaultSubject; @@ -432,9 +463,19 @@ public class ExtendedEmailPublisher extends Notifier { */ private String defaultBody; + /** + * This indicates that the global default body or subject line should be evaluated as a script. + */ + private boolean defaultIsScript; + + /** + * This just remembers the last build for testing that was saved, for the user's convenience. + */ + public String defaultBuildForTesting; + private boolean overrideGlobalSettings; - - @Override + + @Override public String getDisplayName() { return "Editable Email Notification"; } @@ -529,9 +570,13 @@ public class ExtendedEmailPublisher extends Notifier { public String getSmtpPort() { return smtpPort; } - - public String getDefaultContentType() { - return defaultContentType; + + public String getDefaultContentType() { + return defaultContentType; + } + + public String getDefaultCharset() { + return defaultCharset; } public String getDefaultSubject() { @@ -541,6 +586,14 @@ public class ExtendedEmailPublisher extends Notifier { public String getDefaultBody() { return defaultBody; } + + public boolean getDefaultIsScript() { + return defaultIsScript; + } + + public String getDefaultBuildForTesting() { + return defaultBuildForTesting; + } public boolean getOverrideGlobalSettings() { return overrideGlobalSettings; @@ -559,10 +612,13 @@ public class ExtendedEmailPublisher extends Notifier { ExtendedEmailPublisher m = new ExtendedEmailPublisher(); m.recipientList = listRecipients; m.contentType = formData.getString("project_content_type"); - m.defaultSubject = formData.getString("project_default_subject"); + m.charset = formData.getString("project_charset"); + m.defaultSubject = formData.getString("project_default_subject"); m.defaultContent = formData.getString("project_default_content"); - m.configuredTriggers = new ArrayList(); - + m.defaultContentIsScript = formData.optBoolean("project_default_content_is_script"); + m.buildForTesting = formData.getString("project_build_for_testing"); + m.configuredTriggers = new ArrayList(); + // Create a new email trigger for each one that is configured for (String mailerId : EMAIL_TRIGGER_TYPE_MAP.keySet()) { if("true".equalsIgnoreCase(formData.optString("mailer_" + mailerId + "_configured"))) { @@ -571,7 +627,6 @@ public class ExtendedEmailPublisher extends Notifier { m.configuredTriggers.add(trigger); } } - return m; } @@ -584,6 +639,7 @@ public class ExtendedEmailPublisher extends Notifier { m.setSendToRecipientList(formData.optBoolean(prefix + "sendToRecipientList")); m.setSendToDevelopers(formData.optBoolean(prefix + "sendToDevelopers")); m.setIncludeCulprits(formData.optBoolean(prefix + "includeCulprits")); + m.setScript(formData.optBoolean(prefix + "script")); return m; } @@ -630,12 +686,15 @@ public class ExtendedEmailPublisher extends Notifier { smtpPort = nullify(req.getParameter("ext_mailer_smtp_port")); defaultContentType = nullify(req.getParameter("ext_mailer_default_content_type")); + defaultCharset = nullify(req.getParameter("ext_mailer_default_charset")); // Allow global defaults to be set for the subject and body of the email defaultSubject = nullify(req.getParameter("ext_mailer_default_subject")); defaultBody = nullify(req.getParameter("ext_mailer_default_body")); - - overrideGlobalSettings = req.getParameter("ext_mailer_override_global_settings") != null; + defaultIsScript = req.getParameter("ext_mailer_default_is_script") != null; + defaultBuildForTesting = req.getParameter("ext_mailer_default_build_for_testing"); + + overrideGlobalSettings = req.getParameter("ext_mailer_use_global_settings") != null; save(); return super.configure(req, formData); @@ -679,9 +738,129 @@ public class ExtendedEmailPublisher extends Notifier { } return FormValidation.ok(); } - - } + public FormValidation doCharsetCheck(StaplerRequest req, StaplerResponse rsp, @QueryParameter final String value) throws IOException, ServletException { + String charset = nullify(value); + if (charset == null || DEFAULT_CHARSET_SENTINAL.equalsIgnoreCase(charset) || Charset.isSupported(charset)) { + return FormValidation.ok(); + } else { + return FormValidation.error("unsupported charset"); + } + } + + public FormValidation doBuildForTestingCheck(StaplerRequest req, StaplerResponse rsp, @QueryParameter final String value) throws IOException, ServletException { + String buildForTesting = nullify(value); + if (buildForTesting == null) { + return FormValidation.ok(); + } + try { + getBuildForTesting(buildForTesting); + return FormValidation.ok(); + } + catch (FormValidation e) { + return e; + } + } + + // validateButton in config.jelly + public FormValidation doTestAgainstBuild(StaplerRequest req) throws IOException, ServletException { + ExtendedEmailPublisher publisher = new ExtendedEmailPublisher(); + publisher.contentType = req.getParameter("project_content_type"); + publisher.charset = req.getParameter("project_charset"); + publisher.defaultSubject = req.getParameter("project_default_subject"); + publisher.defaultContent = req.getParameter("project_default_content"); + publisher.defaultContentIsScript = req.getParameter("project_default_content_is_script") != null; + publisher.buildForTesting = req.getParameter("project_build_for_testing"); + return doTestAgainstBuild(publisher, false, req); + } + + // validateButton in global.jelly + public FormValidation doGlobalTestAgainstBuild(StaplerRequest req) throws IOException, ServletException { + ExtendedEmailPublisher publisher = new ExtendedEmailPublisher(); + // testing at project level because the corresponding globals are static + publisher.contentType = req.getParameter("ext_mailer_default_content_type"); + publisher.charset = req.getParameter("ext_mailer_default_charset"); + publisher.defaultSubject = req.getParameter("ext_mailer_default_subject"); + publisher.defaultContent = req.getParameter("ext_mailer_default_body"); + publisher.defaultContentIsScript = req.getParameter("ext_mailer_default_is_script") != null; + publisher.buildForTesting = req.getParameter("ext_mailer_default_build_for_testing"); + return doTestAgainstBuild(publisher, true, req); + } + + // for iframe callback + private String testedEmailText; + private String testedEmailContentType; + + private FormValidation doTestAgainstBuild(ExtendedEmailPublisher publisher, + boolean globallyResolved, + StaplerRequest req + ) throws FormValidation { + if (nullify(publisher.buildForTesting) == null) { + return FormValidation.error("need to specify a build for testing"); + } + testedEmailContentType = publisher.getContentType(); + AbstractBuild build = getBuildForTesting(publisher.buildForTesting); + String subject; + if (globallyResolved) { + // Work around ContentBuilder.transformText()'s static access of the global subject and body, + // which has not been updated before testing. + subject = transformResolvedText(publisher.defaultSubject, publisher, build); + testedEmailText = transformResolvedText(publisher.defaultContent, publisher, build); + } else { + subject = transformText(publisher.defaultSubject, publisher, build); + testedEmailText = transformText(publisher.defaultContent, publisher, build); + } + String resultUrl = req.getRequestURI().replace("testAgainstBuild", "testedEmailText") + .replace("globalTestAgainstBuild", "testedEmailText"); // todo: something less hacky? + return FormValidation.okWithMarkup("resulting subject: " + subject // todo: subject charset? + + "
resulting body: