diff --git a/pom.xml b/pom.xml index 63db319..9f6d8c9 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 1.4.5 - + ashlux @@ -39,7 +39,7 @@ org.jvnet.hudson.main maven-plugin - + javax.servlet servlet-api @@ -71,6 +71,12 @@ 1.8.5 test + + + org.codehaus.groovy + groovy-all + 1.5.6 + org.powermock.modules powermock-module-junit4 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 6800dae..afb15d8 100644 --- a/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisher.java +++ b/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisher.java @@ -3,10 +3,7 @@ package hudson.plugins.emailext; import hudson.EnvVars; import hudson.Extension; import hudson.Launcher; -import hudson.model.AbstractBuild; -import hudson.model.BuildListener; -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; @@ -17,6 +14,9 @@ import hudson.tasks.MailMessageIdAction; import hudson.tasks.Mailer; import hudson.tasks.Notifier; import hudson.tasks.Publisher; +import hudson.util.FormValidation; +import net.sf.json.JSONObject; +import org.kohsuke.stapler.*; import javax.mail.Address; import javax.mail.Message; @@ -37,6 +37,10 @@ import java.util.Map; 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. @@ -56,6 +60,8 @@ public class ExtendedEmailPublisher extends Notifier { public static final String CHARSET = "utf-8"; + public 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 " + @@ -102,6 +108,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; @@ -111,6 +122,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. */ @@ -248,8 +263,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 ); @@ -304,18 +318,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, 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, build); + msg.setContent(text, getContentType()); + } + public String getContentType() { String messageContentType = contentType; // contentType is null if the project was not reconfigured after upgrading. if (messageContentType == null || "default".equals(messageContentType)) { @@ -326,9 +359,8 @@ public class ExtendedEmailPublisher extends Notifier { messageContentType = "text/plain"; } } - messageContentType += "; charset=" + CHARSET; - - msg.setContent(text, messageContentType); + messageContentType += "; charset=" + getCharset(); + return messageContentType; } private static void addAddressesFromRecipientList(Set addresses, String recipientList, @@ -356,6 +388,7 @@ public class ExtendedEmailPublisher extends Notifier { return DESCRIPTOR; } + @Extension public static final ExtendedEmailPublisherDescriptor DESCRIPTOR = new ExtendedEmailPublisherDescriptor(); diff --git a/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisherDescriptor.java b/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisherDescriptor.java index c175efe..f9f8af4 100644 --- a/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisherDescriptor.java +++ b/src/main/java/hudson/plugins/emailext/ExtendedEmailPublisherDescriptor.java @@ -1,7 +1,10 @@ package hudson.plugins.emailext; +import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Hudson; +import hudson.model.Job; +import hudson.plugins.emailext.plugins.ContentBuilder; import hudson.plugins.emailext.plugins.EmailTrigger; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Publisher; @@ -10,6 +13,7 @@ import hudson.util.Secret; import net.sf.json.JSONObject; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; import javax.mail.Authenticator; import javax.mail.PasswordAuthentication; @@ -18,6 +22,7 @@ import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.servlet.ServletException; import java.io.IOException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Properties; @@ -73,6 +78,11 @@ public class ExtendedEmailPublisherDescriptor */ 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. */ @@ -83,6 +93,16 @@ public class ExtendedEmailPublisherDescriptor */ 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 @@ -106,6 +126,11 @@ public class ExtendedEmailPublisherDescriptor return defaultSuffix; } + public String getDefaultCharset() + { + return defaultCharset; + } + /** * JavaMail session. */ @@ -166,7 +191,7 @@ public class ExtendedEmailPublisherDescriptor public String getHudsonUrl() { - return hudsonUrl; + return hudsonUrl != null ? hudsonUrl : Hudson.getInstance().getRootUrl(); } public String getSmtpServer() @@ -209,6 +234,14 @@ public class ExtendedEmailPublisherDescriptor return defaultBody; } + public boolean getDefaultIsScript() { + return defaultIsScript; + } + + public String getDefaultBuildForTesting() { + return defaultBuildForTesting; + } + public boolean getOverrideGlobalSettings() { return overrideGlobalSettings; @@ -230,8 +263,11 @@ public class ExtendedEmailPublisherDescriptor ExtendedEmailPublisher m = new ExtendedEmailPublisher(); m.recipientList = listRecipients; m.contentType = formData.getString( "project_content_type" ); + m.charset = formData.getString("project_charset"); m.defaultSubject = formData.getString( "project_default_subject" ); m.defaultContent = formData.getString( "project_default_content" ); + 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 @@ -314,10 +350,13 @@ public class ExtendedEmailPublisherDescriptor 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" ) ); + defaultIsScript = req.getParameter("ext_mailer_default_is_script") != null; + defaultBuildForTesting = req.getParameter("ext_mailer_default_build_for_testing"); overrideGlobalSettings = req.getParameter( "ext_mailer_override_global_settings" ) != null; @@ -358,6 +397,144 @@ public class ExtendedEmailPublisherDescriptor throws IOException, ServletException { return new EmailRecepientUtils().validateFormRecipientList( value ); - } + } + + public FormValidation doCharsetCheck(StaplerRequest req, StaplerResponse rsp, @QueryParameter final String value) throws IOException, ServletException { + String charset = nullify(value); + if (charset == null || ExtendedEmailPublisher.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 = Boolean.valueOf(req.getParameter("project_default_content_is_script")); + 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 = Boolean.valueOf(req.getParameter("ext_mailer_default_is_script")); + 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 { + // use default tokens to induce resolution for project-level script + // flag + subject = transformText( + ExtendedEmailPublisher.PROJECT_DEFAULT_SUBJECT_TEXT, + publisher, build); + testedEmailText = transformText( + ExtendedEmailPublisher.PROJECT_DEFAULT_BODY_TEXT, + 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: