package hudson.plugins.emailext.plugins; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.plugins.emailext.EmailExtException; import hudson.plugins.emailext.EmailType; import hudson.plugins.emailext.ExtendedEmailPublisher; import hudson.plugins.emailext.Util; import hudson.tasks.Mailer; import hudson.tasks.Publisher; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * {@link Publisher} that sends notification e-mail. * * @author kyle.sweeney@valtech.com * */ public class ContentBuilder { private static final Logger LOGGER = Logger.getLogger(Mailer.class.getName()); private static final String DEFAULT_BODY = "\\$DEFAULT_CONTENT|\\$\\{DEFAULT_CONTENT\\}"; private static final String DEFAULT_SUBJECT = "\\$DEFAULT_SUBJECT|\\$\\{DEFAULT_SUBJECT\\}"; private static final String PROJECT_DEFAULT_BODY = "\\$PROJECT_DEFAULT_CONTENT|\\$\\{PROJECT_DEFAULT_CONTENT\\}"; private static final String PROJECT_DEFAULT_SUBJECT = "\\$PROJECT_DEFAULT_SUBJECT|\\$\\{PROJECT_DEFAULT_SUBJECT\\}"; private static final Map<String,EmailContent> EMAIL_CONTENT_TYPE_MAP = new LinkedHashMap<String,EmailContent>(); public static void addEmailContentType(EmailContent contentType) throws EmailExtException { if (EMAIL_CONTENT_TYPE_MAP.containsKey(contentType.getToken())) { throw new EmailExtException("An email content type with token name " + contentType.getToken() + " was already added."); } EMAIL_CONTENT_TYPE_MAP.put(contentType.getToken(), contentType); } public static void removeEmailContentType(EmailContent contentType) { if(EMAIL_CONTENT_TYPE_MAP.containsKey(contentType.getToken())) { EMAIL_CONTENT_TYPE_MAP.remove(contentType); } } public static EmailContent getEmailContentType(String token) { return EMAIL_CONTENT_TYPE_MAP.get(token); } public static Collection<EmailContent> getEmailContentTypes() { return EMAIL_CONTENT_TYPE_MAP.values(); } public String transformText(String origText, ExtendedEmailPublisher publisher, EmailType type, AbstractBuild<?,?> build) { String newText = origText.replaceAll(PROJECT_DEFAULT_BODY, Matcher.quoteReplacement(publisher.defaultContent)) .replaceAll(PROJECT_DEFAULT_SUBJECT, Matcher.quoteReplacement(publisher.defaultSubject)) .replaceAll(DEFAULT_BODY, Matcher.quoteReplacement(ExtendedEmailPublisher.DESCRIPTOR.getDefaultBody())) .replaceAll(DEFAULT_SUBJECT, Matcher.quoteReplacement(ExtendedEmailPublisher.DESCRIPTOR.getDefaultSubject())); newText = replaceTokensWithContent(newText, publisher, type, build); return newText; } private static <P extends AbstractProject<P, B>, B extends AbstractBuild<P, B>> String replaceTokensWithContent(String origText, ExtendedEmailPublisher publisher, EmailType type, AbstractBuild<P, B> build) { StringBuffer sb = new StringBuffer(); Tokenizer tokenizer = new Tokenizer(origText); while (tokenizer.find()) { String tokenName = tokenizer.getTokenName(); Map<String, Object> args = tokenizer.getArgs(); EmailContent content = EMAIL_CONTENT_TYPE_MAP.get(tokenName); String replacement; if (content != null) { try { replacement = content.getContent(build, publisher, type, args); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Exception thrown while replacing " + tokenizer.group(), e); replacement = "[[ Exception while replacing " + tokenName + ". Please report this as a bug. ]]"; } if (content.hasNestedContent()) { replacement = replaceTokensWithContent(replacement, publisher, type, build); } } else { replacement = tokenizer.group(); } tokenizer.appendReplacement(sb, replacement); } tokenizer.appendTail(sb); return sb.toString(); } static class Tokenizer { private static final String tokenNameRegex = "[a-zA-Z0-9_]+"; private static final String numberRegex = "-?[0-9]+(\\.[0-9]*)?"; private static final String boolRegex = "(true)|(false)"; // Sequence of (1) not \ " CR LF and (2) \ followed by non line terminator private static final String stringRegex = "\"([^\\\\\"\\r\\n]|(\\\\.))*\""; private static final String valueRegex = "(" + numberRegex + ")|(" + boolRegex + ")|(" + stringRegex + ")"; private static final String spaceRegex = "[ \\t]*"; private static final String argRegex = "(" + tokenNameRegex + ")" + spaceRegex + "=" + spaceRegex + "(" + valueRegex + ")"; private static final String argsRegex = "((" + spaceRegex + "," + spaceRegex + argRegex + ")*)"; private static final String delimitedTokenRegex = "\\{" + spaceRegex + "(" + tokenNameRegex + ")" + argsRegex + spaceRegex + "\\}"; private static final String tokenRegex = "\\$((" + tokenNameRegex + ")|(" + delimitedTokenRegex + "))"; private static final Pattern argPattern = Pattern.compile(argRegex); private static final Pattern tokenPattern = Pattern.compile(tokenRegex); private final Matcher tokenMatcher; private String tokenName = null; private Map<String, Object> args = null; Tokenizer(String origText) { tokenMatcher = tokenPattern.matcher(origText); } String getTokenName() { return tokenName; } Map<String, Object> getArgs() { return args; } String group() { return tokenMatcher.group(); } boolean find() { if (tokenMatcher.find()) { tokenName = tokenMatcher.group(2); if (tokenName == null) { tokenName = tokenMatcher.group(4); } args = new HashMap<String, Object>(); if (tokenMatcher.group(5) != null) { parseArgs(tokenMatcher.group(5), args); } return true; } else { return false; } } static void parseArgs(String argsString, Map<String, Object> args) { Matcher argMatcher = argPattern.matcher(argsString); while (argMatcher.find()) { Object arg; if (argMatcher.group(3) != null) { // number if (argMatcher.group(4) != null) { arg = Float.valueOf(argMatcher.group(3)); } else { arg = Integer.valueOf(argMatcher.group(3)); } } else if (argMatcher.group(5) != null) { // boolean if (argMatcher.group(6) != null) { arg = Boolean.TRUE; } else { arg = Boolean.FALSE; } } else { // if (argMatcher.group(8) != null) { // string arg = Util.unescapeString(argMatcher.group(8)); } args.put(argMatcher.group(1), arg); } } void appendReplacement(StringBuffer sb, String replacement) { tokenMatcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); } void appendTail(StringBuffer sb) { tokenMatcher.appendTail(sb); } } }