diff --git a/src/main/java/org/nexial/core/plugins/mail/EmailSettings.java b/src/main/java/org/nexial/core/plugins/mail/EmailSettings.java index 96ac040b0..c684f24e7 100644 --- a/src/main/java/org/nexial/core/plugins/mail/EmailSettings.java +++ b/src/main/java/org/nexial/core/plugins/mail/EmailSettings.java @@ -18,9 +18,12 @@ package org.nexial.core.plugins.mail; import java.io.File; +import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; /** @@ -33,48 +36,100 @@ public class EmailSettings { private String subject; private String body; private Map attachments; - private String failure; + private Map failures; - public List getToRecipients() { return toRecipients; } + public List getToRecipients() { + return toRecipients; + } - public void setToRecipients(List toRecipients) { this.toRecipients = toRecipients; } + public void setToRecipients(List toRecipients) { + this.toRecipients = toRecipients; + } - public List getCcRecipients() {return ccRecipients; } + public List getCcRecipients() { + return ccRecipients; + } - public void setCcRecipients(List ccRecipients) { this.ccRecipients = ccRecipients; } + public void setCcRecipients(List ccRecipients) { + this.ccRecipients = ccRecipients; + } - public List getBccRecipients() {return bccRecipients; } + public List getBccRecipients() { + return bccRecipients; + } - public void setBccRecipients(List bccRecipients) { this.bccRecipients = bccRecipients; } + public void setBccRecipients(List bccRecipients) { + this.bccRecipients = bccRecipients; + } - public String getSubject() {return subject; } + public String getSubject() { + return subject; + } - public void setSubject(String subject) { this.subject = subject; } + public void setSubject(String subject) { + this.subject = subject; + } - public String getBody() {return body; } + public String getBody() { + return body; + } - public void setBody(String body) { this.body = body; } + public void setBody(String body) { + this.body = body; + } - public String getFailure() {return failure; } + public Map getFailures() { + return failures; + } - public void setFailure(String failure) { this.failure = failure; } + public void setFailure(String config, String message) { + if (!MapUtils.isNotEmpty(failures)) { + failures = new HashMap<>(); + } + failures.put(config, message); + } - public void clearFailure() { this.failure = null; } + public void clearFailure(String config) { + if (MapUtils.isNotEmpty(failures)) { + failures.remove(config); + } + } - public Map getAttachments() {return attachments; } + public Map getAttachments() { + return attachments; + } - public void setAttachments(Map attachments) { this.attachments = attachments; } + public void setAttachments(Map attachments) { + this.attachments = attachments; + } @Override public String toString() { return new ToStringBuilder(this) - .append("toRecipients", toRecipients) - .append("ccRecipients", ccRecipients) - .append("bccRecipients", bccRecipients) - .append("subject", subject) - .append("body", body) - .append("attachments", attachments) - .append("failure", failure) - .toString(); + .append("toRecipients", toRecipients) + .append("ccRecipients", ccRecipients) + .append("bccRecipients", bccRecipients) + .append("subject", subject) + .append("body", body) + .append("attachments", attachments) + .append("failures are:-\n", failures) + .toString(); + } + + /** + * Displays all the failures that occurred in a String format. + * + * @return all the failures. + */ + public String displayFailures() { + if (MapUtils.isEmpty(failures)) { + return StringUtils.EMPTY; + } + + StringBuilder messages = new StringBuilder(StringUtils.EMPTY); + for (String key : failures.keySet()) { + messages.append(failures.get(key)).append("\n"); + } + return messages.toString(); } } diff --git a/src/main/java/org/nexial/core/plugins/mail/MailCommand.java b/src/main/java/org/nexial/core/plugins/mail/MailCommand.java index 7212f5006..ca16fec7e 100755 --- a/src/main/java/org/nexial/core/plugins/mail/MailCommand.java +++ b/src/main/java/org/nexial/core/plugins/mail/MailCommand.java @@ -17,13 +17,6 @@ package org.nexial.core.plugins.mail; -import java.io.File; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import javax.mail.MessagingException; -import javax.validation.constraints.NotNull; - import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; @@ -34,10 +27,19 @@ import org.nexial.core.model.ExecutionDefinition; import org.nexial.core.model.StepResult; import org.nexial.core.plugins.base.BaseCommand; +import org.nexial.core.utils.OutputResolver; + +import javax.mail.MessagingException; +import javax.validation.constraints.NotNull; +import java.io.File; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.nexial.core.NexialConst.Iteration.CURR_ITERATION_ID; import static org.nexial.core.NexialConst.Project.SCRIPT_FILE_EXT; import static org.nexial.core.utils.CheckUtils.requiresNotBlank; +import static org.nexial.core.utils.CheckUtils.requiresNotNull; /** * Command for email composition and dispatching it. @@ -46,14 +48,14 @@ */ public class MailCommand extends BaseCommand { private static final String VAR_NAMESPACE = "nexial.email"; - private static final String SUBJECT = "set-subject"; - private static final String BODY = "add-body"; - private static final String ATTACH = "add-attachment"; - private static final String TO = "add-to"; - private static final String CC = "add-cc"; - private static final String BCC = "add-bcc"; - private static final Set ACTIONS = Stream.of(SUBJECT, TO, CC, BCC, BODY, ATTACH) - .collect(Collectors.toCollection(HashSet::new)); + private static final String SUBJECT = "subject"; + private static final String BODY = "body"; + private static final String ATTACH = "attachment"; + private static final String TO = "to"; + private static final String CC = "cc"; + private static final String BCC = "bcc"; + private static final Set CONFIGS = Stream.of(SUBJECT, TO, CC, BCC, BODY, ATTACH) + .collect(Collectors.toCollection(HashSet::new)); private static final String ATTACHMENT_DELIM = "="; private static final String EMAIL_DELIM = ","; private static final long MAX_FILE_SIZE = 10L * 1024L * 1024L; // 10MB. @@ -62,31 +64,80 @@ public class MailCommand extends BaseCommand { private enum Recipients {TO, CC, BCC} @Override - public String getTarget() { return "mail"; } + public String getTarget() { + return "mail"; + } @Override - public void init(ExecutionContext context) { super.init(context); } + public void init(ExecutionContext context) { + super.init(context); + } + + /** + * Sends an email based on the properties set. + * + * @param profile name of the email profile. + * @param to the TO recipient. + * @param subject the subject of the email. + * @param body email body. + * @return {@link StepResult#success(String)} or {@link StepResult#fail(String)} + */ + public StepResult send(String profile, String to, String subject, String body) { + requiresNotBlank(profile, "Invalid profile", profile); + requiresNotBlank(to, "Invalid recipient", to); + requiresNotBlank(subject, "Invalid subject", subject); + requiresNotBlank(body, "Invalid email body", body); + + MailProfile settings = MailProfile.newInstance(profile); + requiresNotNull(settings, "Unable to derive email connectivity from profile '" + profile + "'"); + + MailObjectSupport mailer = new MailObjectSupport(); + mailer.setMailProps(settings.toProperties()); + MailSender sender = MailSender.newInstance(mailer); + String from = settings.getFrom(); + String[] recipients = StringUtils.split(StringUtils.replace(to, ";", ","), ","); + + try { + // body = OutputFileUtils.resolveContent(body, context, false, true); + body = new OutputResolver(body, context).getContent(); + sender.sendMail(recipients, from, subject, body); + return StepResult.success("email successfully sent"); + } catch (Throwable e) { + return StepResult.fail("email unsuccessful to be sent: " + e.getMessage()); + } + } + + /** + * Check if the email address is valid or not. + * + * @param address email address passed in. + * @return true/false based on whether the email address is valid or not. + */ + protected boolean isValidEmail(String address) { + return StringUtils.isNotBlank(address) && EmailValidator.getInstance().isValid(address.trim()); + } /** * This command refers to various steps involved in composing the email like adding the Subject of email, * adding the TO recipients, CC recipients, BCC recipients, add Attachments to the email, add the Body to * the email etc. In this command the var refers to the variable containing the name associated to the email. - * The action specifies what to perform. For example attach some file to email, attach the screen shot to the email, - * add the subject to the email etc. The value is associated to the action associated for example the path of the - * file attachment, subject of the email etc. based on the action to be performed. Once the basic implementation + * The config specifies what to perform. For example attach some file to email, attach the screen shot to the email, + * add the subject to the email etc. The value is associated to the config associated for example the path of the + * file attachment, subject of the email etc. based on the config to be performed. Once the basic implementation * is done features like adding the email priority will be added to the email. The command is flexible such that we * can update any property of the email. Also we can reuse the same var set as part of the command to configure * various emails. * * @param var the variable name containing the email information. - * @param action the action associated as part of email composition. - * This is one among the {@link MailCommand#ACTIONS}. - * @param value the value to be used in regard to the action associated. + * @param config the config associated as part of email composition. + * This is one among the {@link MailCommand#CONFIGS}. + * @param value the value to be used in regard to the config associated. * @return {@link StepResult#success(String)} displaying the email composition details set so far. */ - public StepResult composeMail(final String var, final String action, final String value) { - requiresNotBlank(value, "Value cannot be empty", value); - if (!ACTIONS.contains(action)) { return StepResult.fail("Action '" + action + "' is not valid."); } + public StepResult compose(final String var, final String config, final String value) { + if (!CONFIGS.contains(config)) { + return StepResult.fail("Config '" + config + "' is not valid."); + } String variableName = getContextVariable(var); Object mailContext = context.getObjectData(variableName); @@ -100,14 +151,8 @@ public StepResult composeMail(final String var, final String action, final Strin return StepResult.fail("Variable with the name `" + var + "` is not a valid email configuration."); } - // todo: how to clear previous failure? - // maybe it's ok, we can let this go and let user a chance to fix previous mistake - if (StringUtils.isNotEmpty(emailSettings.getFailure())) { - return StepResult.fail("Ignoring this step as the Email composition failed earlier."); - } - context.setData(variableName, emailSettings); - return performAction(action, value.trim(), emailSettings); + return setConfiguration(config, value.trim(), emailSettings); } /** @@ -119,24 +164,24 @@ public StepResult composeMail(final String var, final String action, final Strin private String getContextVariable(@NotNull final String var) { ExecutionDefinition execDef = context.getExecDef(); return StringUtils.joinWith( - ".", - VAR_NAMESPACE, - String.valueOf(execDef.getPlanSequence()), - StringUtils.removeEndIgnoreCase(new File(execDef.getTestScript()).getName(), SCRIPT_FILE_EXT), - context.getCurrentScenario(), - context.getStringData(CURR_ITERATION_ID), - var); + ".", + VAR_NAMESPACE, + String.valueOf(execDef.getPlanSequence()), + StringUtils.removeEndIgnoreCase(new File(execDef.getTestScript()).getName(), SCRIPT_FILE_EXT), + context.getCurrentScenario(), + context.getStringData(CURR_ITERATION_ID), + var); } /** * This command is used to send the email based on the profile settings as well as the configurations set as part of - * {@link MailCommand#composeMail(String, String, String)} related var. + * {@link MailCommand#compose(String, String, String)} related var. * * @param profile mail profile set as part of data file. * @param var name of the mail configuration variable. * @return {@link StepResult} stating whether the email is sent successfully or failed due to some reason. */ - public StepResult send(final String profile, final String var) { + public StepResult sendComposed(final String profile, final String var) { requiresNotBlank(profile, "Invalid profile", profile); Object objectData = context.getObjectData(getContextVariable(var)); @@ -146,15 +191,20 @@ public StepResult send(final String profile, final String var) { } EmailSettings emailSettings = (EmailSettings) objectData; - if (StringUtils.isNotEmpty(emailSettings.getFailure())) { - return StepResult.fail("Unable to send email; " + emailSettings.getFailure()); + + if (MapUtils.isNotEmpty(emailSettings.getFailures())) { + return StepResult.fail("Unable to send email; " + emailSettings.displayFailures()); } String validationErrorMessage = validateEmailSettings(emailSettings); - if (StringUtils.isNotEmpty(validationErrorMessage)) { return StepResult.fail(validationErrorMessage); } + if (StringUtils.isNotEmpty(validationErrorMessage)) { + return StepResult.fail(validationErrorMessage); + } MailProfile mailProfile = MailProfile.newInstance(profile); - if (mailProfile == null) { return StepResult.fail("Invalid profile '" + profile + "'"); } + if (mailProfile == null) { + return StepResult.fail("Invalid profile '" + profile + "'"); + } return sendEmail(profile, emailSettings, mailProfile, new MailObjectSupport()); } @@ -166,7 +216,7 @@ public StepResult send(final String profile, final String var) { * @param var the name of the mail configuration variable. * @return {@link StepResult} based on whether the variable exists or not. */ - public StepResult clearMail(final String var) { + public StepResult clearComposed(final String var) { String variable = getContextVariable(var); Object object = context.getObjectData(variable); @@ -178,48 +228,81 @@ public StepResult clearMail(final String var) { } /** - * Perform the appropriate email action. + * Perform the appropriate email config. * - * @param action the email action performed i.e. one of the actions in {@link MailCommand#ACTIONS}. - * @param value the value passed to the action. + * @param config the email config set i.e. one of the configs in {@link MailCommand#CONFIGS}. + * @param value the value passed to the config. * @param emailSettings {@link EmailSettings}. - * @return {@link StepResult#success(String)} or {@link StepResult#fail(String)} based on if the action successful - * or any of the validations failed. + * @return {@link StepResult#success(String)} or {@link StepResult#fail(String)} based on if the config successful + * or any of the validations failed. */ - private StepResult performAction(@NotNull final String action, - @NotNull final String value, - @NotNull final EmailSettings emailSettings) { + private StepResult setConfiguration(@NotNull final String config, + @NotNull final String value, + @NotNull final EmailSettings emailSettings) { // todo: support content type // todo: support reply-to - switch (action) { + boolean valueReset = context.isNullOrEmptyValue(value); + switch (config) { case SUBJECT: + emailSettings.clearFailure(SUBJECT); + if (valueReset) { + emailSettings.setSubject(null); + return StepResult.success(emailSettings.toString()); + } emailSettings.setSubject(value); return StepResult.success(emailSettings.toString()); case BODY: - emailSettings.setBody(StringUtils.defaultString(emailSettings.getBody()).concat(value)); + emailSettings.clearFailure(BODY); + if (valueReset) { + emailSettings.setBody(null); + return StepResult.success(emailSettings.toString()); + } + + emailSettings.setBody(value); return StepResult.success(emailSettings.toString()); case TO: - return configureMailRecipients(value, emailSettings, Recipients.TO) ? - StepResult.success(emailSettings.toString()) : StepResult.fail(emailSettings.getFailure()); + emailSettings.clearFailure(TO); + if (valueReset) { + emailSettings.setToRecipients(null); + return StepResult.success(emailSettings.toString()); + } + + configureMailRecipients(value, emailSettings, Recipients.TO); + return StepResult.success(emailSettings.toString()); case CC: - return configureMailRecipients(value, emailSettings, Recipients.CC) ? - StepResult.success(emailSettings.toString()) : StepResult.fail(emailSettings.getFailure()); + emailSettings.clearFailure(CC); + if (valueReset) { + emailSettings.setCcRecipients(null); + return StepResult.success(emailSettings.toString()); + } + configureMailRecipients(value, emailSettings, Recipients.CC); + return StepResult.success(emailSettings.toString()); case BCC: - return configureMailRecipients(value, emailSettings, Recipients.BCC) ? - StepResult.success(emailSettings.toString()) : StepResult.fail(emailSettings.getFailure()); + emailSettings.clearFailure(BCC); + if (valueReset) { + emailSettings.setBccRecipients(null); + return StepResult.success(emailSettings.toString()); + } + configureMailRecipients(value, emailSettings, Recipients.BCC); + return StepResult.success(emailSettings.toString()); case ATTACH: - return addAttachments(value.trim(), emailSettings) ? - StepResult.success(emailSettings.toString()) : StepResult.fail(emailSettings.getFailure()); + emailSettings.clearFailure(ATTACH); + if (valueReset) { + emailSettings.setAttachments(null); + return StepResult.success(emailSettings.toString()); + } + addAttachments(value.trim(), emailSettings); + return StepResult.success(emailSettings.toString()); default: - String message = "Invalid action " + action + "."; - emailSettings.setFailure(message); + String message = "Invalid config " + config + "."; + emailSettings.setFailure(config, message); return StepResult.fail(message); } @@ -230,10 +313,14 @@ private StepResult performAction(@NotNull final String action, * * @param value list of attachments separated by {@link #ATTACHMENT_DELIM} * @param emailSettings {@link EmailSettings} passed in. - * @return true/false based on whether the attachments added successfully or not. */ - private boolean addAttachments(@NotNull final String value, @NotNull final EmailSettings emailSettings) { + private void addAttachments(@NotNull final String value, @NotNull final EmailSettings emailSettings) { + emailSettings.setAttachments(null); String[] filePaths = StringUtils.split(value, context.getTextDelim()); + if (filePaths.length == 0) { + return; + } + List invalidFilePaths = new ArrayList<>(); Map fileAttachments = new HashMap<>(); @@ -249,86 +336,69 @@ private boolean addAttachments(@NotNull final String value, @NotNull final Email fileAttachments.put(file.getName(), file); } - if (!file.exists()) { invalidFilePaths.add(filePath); } + if (!file.exists()) { + invalidFilePaths.add(filePath); + } } if (CollectionUtils.isNotEmpty(invalidFilePaths)) { String message = "The following file(s) are not valid " + invalidFilePaths + "."; - emailSettings.setFailure(message); - return false; + emailSettings.setFailure(ATTACH, message); } - if (MapUtils.isNotEmpty(emailSettings.getAttachments())) { - emailSettings.getAttachments().putAll(fileAttachments); - } else { - emailSettings.setAttachments(fileAttachments); - } + emailSettings.setAttachments(fileAttachments); long filesSize = emailSettings.getAttachments().values().stream().map(File::length).reduce(0L, Long::sum); if (filesSize > MAX_FILE_SIZE) { - emailSettings.setFailure("Files size attached is greater than max size of 10MB."); - return false; + emailSettings.setFailure(ATTACH, "Files size attached is greater than max size of 10MB."); } - return true; } /** * Configures the email recipients based on the Recipient types. Checks if there are any failures already in the * mail composition. Also checks if there are any emails which are not passing the - * {@link MailCommand#isNotValidEmail(String)}. If all the validations are passed then the emails will be configured + * {@link MailCommand#isValidEmail(String)}. If all the validations are passed then the emails will be configured * accordingly. * * @param value the recipients list passed in a String separated by a {@link #EMAIL_DELIM} * @param emailSettings {@link EmailSettings} passed in. * @param recipientType recipient type passed in. - * @return true/false based on whether the validations passed and configurations set properly or not. */ - private boolean configureMailRecipients(@NotNull final String value, - @NotNull final EmailSettings emailSettings, - @NotNull final Recipients recipientType) { + private void configureMailRecipients(@NotNull final String value, + @NotNull final EmailSettings emailSettings, + @NotNull final Recipients recipientType) { // ignore empty email - if (StringUtils.isEmpty(value)) { return true; } + if (StringUtils.isEmpty(value)) { + return; + } List emails = Arrays.stream(StringUtils.split(value, EMAIL_DELIM)) - .map(String::trim) - .collect(Collectors.toList()); + .map(String::trim) + .collect(Collectors.toList()); + + String config = recipientType.name().toLowerCase(); if (CollectionUtils.isEmpty(emails)) { - emailSettings.setFailure("Invalid email address(es) for " + recipientType.name() + " recipients."); - return false; + emailSettings.setFailure(config, "Invalid email address(es) for " + recipientType.name() + " recipients."); } - List invalidEmails = emails.stream().filter(this::isNotValidEmail).collect(Collectors.toList()); + List invalidEmails = emails.stream() + .filter(address -> !isValidEmail(address)).collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(invalidEmails)) { - emailSettings.setFailure("The invalid " + recipientType.name() + " recipients: " + invalidEmails); - return false; + emailSettings.setFailure(config, "The invalid " + recipientType.name() + " recipients: " + invalidEmails); } switch (recipientType) { case TO: - emailSettings.setToRecipients(appendPreviousRecipients(emails, emailSettings.getToRecipients())); + emailSettings.setToRecipients(emails); break; case CC: - emailSettings.setCcRecipients(appendPreviousRecipients(emails, emailSettings.getCcRecipients())); + emailSettings.setCcRecipients(emails); break; case BCC: - emailSettings.setBccRecipients(appendPreviousRecipients(emails, emailSettings.getBccRecipients())); + emailSettings.setBccRecipients(emails); break; } - - return true; - } - - /** - * Add the existing recipients to the current recipients. - * - * @param emails existing list of recipients - * @param recipients new list of recipients - * @return {@link List} containing all the list of recipients. - */ - private List appendPreviousRecipients(final List emails, final List recipients) { - if (CollectionUtils.isNotEmpty(recipients)) { emails.addAll(recipients); } - return emails; } /** @@ -339,7 +409,7 @@ private List appendPreviousRecipients(final List emails, final L * @param mailProfile the {@link MailProfile} configurations passed in. * @param mailer the {@link MailObjectSupport} * @return {@link StepResult#success(String)} or {@link StepResult#fail(String)} based on whether email - * got dispatched or not. + * got dispatched or not. */ private StepResult sendEmail(@NotNull final String profile, @NotNull final EmailSettings emailSettings, @@ -355,11 +425,11 @@ private StepResult sendEmail(@NotNull final String profile, try { sender.sendMail(emailSettings.getToRecipients(), - emailSettings.getCcRecipients(), - emailSettings.getBccRecipients(), - from, emailSettings.getSubject(), - emailSettings.getBody(), - emailSettings.getAttachments()); + emailSettings.getCcRecipients(), + emailSettings.getBccRecipients(), + from, emailSettings.getSubject(), + emailSettings.getBody(), + emailSettings.getAttachments()); } catch (MessagingException e) { return StepResult.fail("Email failed to get delivered. Error is " + e.getMessage()); } @@ -367,16 +437,6 @@ private StepResult sendEmail(@NotNull final String profile, return StepResult.success("Email dispatched successfully."); } - /** - * Checks whether the email is valid or not. - * - * @param address email address passed in. - * @return true/false based on whether the email is valid or not. - */ - private boolean isNotValidEmail(final String address) { - return !StringUtils.isNotBlank(address) || !EmailValidator.getInstance().isValid(address.trim()); - } - /** * Checks if there are any validation errors in terms of the email settings that are set during the email * composition. @@ -385,10 +445,18 @@ private boolean isNotValidEmail(final String address) { * @return Failure message in case of validation failure and if not {@link StringUtils#EMPTY}. */ private String validateEmailSettings(@NotNull final EmailSettings emailSettings) { - if (StringUtils.isEmpty(emailSettings.getBody())) { return "No email body found."; } - if (StringUtils.isEmpty(emailSettings.getSubject())) { return "No email subject found."; } - if (CollectionUtils.isEmpty(emailSettings.getToRecipients())) { return "No email recipients configured."; } - if (StringUtils.isNotEmpty(emailSettings.getFailure())) { return emailSettings.getFailure(); } - return ""; + if (StringUtils.isEmpty(emailSettings.getBody())) { + return "No email body found."; + } + if (StringUtils.isEmpty(emailSettings.getSubject())) { + return "No email subject found."; + } + if (CollectionUtils.isEmpty(emailSettings.getToRecipients())) { + return "No email recipients configured."; + } + if (MapUtils.isNotEmpty(emailSettings.getFailures())) { + return emailSettings.getFailures().toString(); + } + return StringUtils.EMPTY; } }