EmailNotificationService.java

package cf.maybelambda.httpvalidator.springboot.service;

import com.sendgrid.Method;
import com.sendgrid.Request;
import com.sendgrid.Response;
import com.sendgrid.SendGrid;
import com.sendgrid.helpers.mail.Mail;
import com.sendgrid.helpers.mail.objects.Content;
import com.sendgrid.helpers.mail.objects.Email;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.rmi.ConnectIOException;
import java.util.List;

import static cf.maybelambda.httpvalidator.springboot.util.HttpSendOutcomeWrapper.NET_ERR_CODE;
import static io.micrometer.common.util.StringUtils.isBlank;
import static io.micrometer.common.util.StringUtils.truncate;
import static java.util.Objects.isNull;

/**
 * Service to send email notifications using SendGrid.
 */
@Service
public class EmailNotificationService {
    static final String BODY_LINE1 = "Request URL: ";
    static final String BODY_LINE2 = "Response Status Code: ";
    static final String BODY_LINE3 = "Response body: ";
    static final String APIKEY_PROPERTY = "sendgrid.apikey";
    static final String FROM_PROPERTY = "notifications.from";
    static final String TO_PROPERTY = "notifications.to";
    private SendGrid client;
    private static Logger logger = LoggerFactory.getLogger(EmailNotificationService.class);

    @Autowired
    private Environment env;

    /**
     * Builds the email body content from a list of validation results.
     *
     * @param contents A list of string arrays containing validation results.
     * @return The email body as a single string.
     */
    String buildMailBody(List<String[]> contents) {
        String res = "";
        for (String[] c : contents) {
            String p0 = BODY_LINE1 + c[0] + "\n";
            if (String.valueOf(NET_ERR_CODE).equals(c[1])) {
                res += p0 + c[2];
            } else {
                String p1 = BODY_LINE2 + c[1] + "\n";
                String p2 = BODY_LINE3 + (c[2] == null ? "" : truncate(c[2], 800, "..."));
                res += p0 + p1 + p2;
            }
            res += "\n\n\n";
        }

        return res;
    }

    /**
     * Sends a plain text email with the specified subject and body.
     *
     * @param subject The subject of the email.
     * @param body The body content of the email.
     * @throws ConnectIOException If an error occurs while sending the email.
     */
    void sendPlainTextEmail(String subject, String body) throws ConnectIOException {
        Email from = new Email(this.getFrom());
        from.setName("Chronos Maybelambda");
        Email to = new Email(this.getTo());
        Content content = new Content("text/plain", body);
        Mail mail = new Mail(from, subject, to, content);

        Request request = new Request();
        request.setMethod(Method.POST);
        request.setEndpoint("mail/send");
        try {
            request.setBody(mail.build());
            if (isNull(this.client)) { this.client = new SendGrid(this.getApiKey()); }
            Response res = this.client.api(request);
            logger.info("Email delivery result: Status Code: {}", res.getStatusCode() + " - Body: " + res.getBody());
        } catch (IOException e) {
            String errmsg = "POST request for delivery of the Notification Email could not be completed.";
            logger.error(errmsg);
            throw new ConnectIOException(errmsg, e);
        }
    }

    /**
     * Sends a notification email with validation task failure results.
     *
     * @param mailBody A list of validation task failure results.
     * @throws ConnectIOException If an error occurs while sending the email.
     */
    public void sendVTaskErrorsNotification(@NonNull List<String[]> mailBody) throws ConnectIOException {
        String subject = "Notification of HTTP validation results";
        String body = this.buildMailBody(mailBody);

        this.sendPlainTextEmail(subject, body);
    }

    /**
     * Sends a notification email about application termination.
     *
     * @param endTime The end time of the application.
     * @throws ConnectIOException If an error occurs while sending the email.
     */
    public void sendAppTerminatedNotification(String endTime) throws ConnectIOException {
        String subject = "HTTP Validator app terminated";
        String body = String.format("ContextClosedEvent occurred on: %s.", endTime);

        this.sendPlainTextEmail(subject, body);
    }

    /**
     * Checks if the email configuration is valid.
     * <p>
     * This method verifies that the necessary properties for sending emails
     * are properly configured in the environment. Specifically, it checks:
     * <ul>
     *     <li>The "notifications.from" property, which should contain the sender's email address.</li>
     *     <li>The "notifications.to" property, which should contain the recipient's email address.</li>
     *     <li>The "sendgrid.apikey" property, which should contain the SendGrid API key for authentication.</li>
     * </ul>
     * If any of these properties are blank or missing, the configuration is considered invalid.
     *
     * @return True if all necessary properties are set and non-blank, false otherwise.
     */
    public boolean isValidConfig() {
        return !(isBlank(this.getFrom()) || isBlank(this.getTo()) || isBlank(this.getApiKey()));
    }

    /**
     * Gets the SendGrid API key from the environment.
     *
     * @return The API key as a string.
     */
    private String getApiKey() { return this.env.getProperty(APIKEY_PROPERTY); }

    /**
     * Gets the email address through which notifications are sent.
     *
     * @return The sender email address.
     */
    String getFrom() { return this.env.getProperty(FROM_PROPERTY); }

    /**
     * Gets the email address to which notifications are sent.
     *
     * @return The recipient email address.
     */
    String getTo() { return this.env.getProperty(TO_PROPERTY); }

    /**
     * Sets the SendGrid client; for testing purposes.
     *
     * @param sg The SendGrid client to set.
     */
    void setClient(SendGrid sg) { this.client = sg; }

    /**
     * Sets the logger; for testing purposes.
     *
     * @param logger The logger to set.
     */
    void setLogger(Logger logger) { EmailNotificationService.logger = logger; }

    /**
     * Sets the environment; for testing purposes.
     *
     * @param env The environment to set.
     */
    void setEnv(Environment env) { this.env = env; }
}