From 146a0fb8b0e90f9196e569152f649baf60d6cc8f Mon Sep 17 00:00:00 2001
From: Joaquín Reñé <jrene@curisit.net>
Date: Tue, 07 Oct 2025 14:52:57 +0000
Subject: [PATCH] #4410 - Comments on classes
---
securis/src/main/java/net/curisit/securis/utils/EmailManager.java | 217 ++++++++++++++++++++++++++++++++++++++++-------------
1 files changed, 163 insertions(+), 54 deletions(-)
diff --git a/securis/src/main/java/net/curisit/securis/utils/EmailManager.java b/securis/src/main/java/net/curisit/securis/utils/EmailManager.java
index 5b3ad1f..4d427f5 100644
--- a/securis/src/main/java/net/curisit/securis/utils/EmailManager.java
+++ b/securis/src/main/java/net/curisit/securis/utils/EmailManager.java
@@ -1,3 +1,6 @@
+/*
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
+ */
package net.curisit.securis.utils;
import java.io.File;
@@ -40,22 +43,61 @@
import org.apache.logging.log4j.Logger;
/**
- * Component that send emails using Mailgun API:
- * http://documentation.mailgun.com/user_manual.html#sending-messages
- *
- * @author roberto <roberto.sanchez@curisit.net>
+ * EmailManager
+ * <p>
+ * Small utility to send plain-text emails (optionally with one attachment)
+ * using the <b>Mailgun</b> API over HTTPS.
+ * <p>
+ * Design notes:
+ * <ul>
+ * <li>Reads Mailgun credentials and the "from" address from {@link Config}.</li>
+ * <li>Builds a preconfigured {@link HttpClientBuilder} with basic auth and a permissive SSL socket factory.</li>
+ * <li>Exposes synchronous (blocking) and asynchronous (non-blocking) send methods.</li>
+ * <li>Scope is {@code @ApplicationScoped}; the underlying builder is created once per container.</li>
+ * </ul>
+ *
+ * Thread-safety:
+ * <p>
+ * The class is effectively stateless after construction; using a shared {@link HttpClientBuilder}
+ * is safe as a new {@link HttpClient} is built per request.
+ *
+ * Configuration keys (see {@link Config.KEYS}):
+ * <ul>
+ * <li>{@code mailgun.domain}</li>
+ * <li>{@code mailgun.api.key}</li>
+ * <li>{@code email.from.address}</li>
+ * </ul>
+ *
+ * Failure handling:
+ * <p>
+ * Network and HTTP errors are surfaced as {@link SeCurisServiceException} with appropriate error codes.
+ *
+ * @author roberto <roberto.sanchez@curisit.net>
+ * Last reviewed by JRA on Oct 6, 2025.
*/
@ApplicationScoped
public class EmailManager {
+ /** Class logger. */
private static final Logger LOG = LogManager.getLogger(EmailManager.class);
+ /** Mailgun endpoint composed from configured domain. */
private final String serverUrl;
+
+ /** Preconfigured builder that carries SSL and basic-auth configuration. */
private final HttpClientBuilder httpClientBuilder;
+ // ---------------------------------------------------------------------
+ // Constructors
+ // ---------------------------------------------------------------------
+
/**
- *
- * @throws SeCurisException
+ * EmailManager
+ * <p>
+ * Default constructor that validates required configuration and prepares an
+ * HTTP client builder with Mailgun credentials and SSL settings.
+ *
+ * @throws SeCurisException if mandatory configuration is missing or the SSL builder cannot be created
*/
public EmailManager() throws SeCurisException {
String domain = Config.get(Config.KEYS.MAILGUN_DOMAIN);
@@ -64,13 +106,29 @@
}
serverUrl = String.format("https://api.mailgun.net/v2/%s/messages", domain);
httpClientBuilder = createHttpClient();
-
}
+ // ---------------------------------------------------------------------
+ // Internal helpers
+ // ---------------------------------------------------------------------
+
+ /**
+ * createHttpClient
+ * <p>
+ * Builds a {@link HttpClientBuilder} that:
+ * <ul>
+ * <li>Accepts any server certificate (permissive trust strategy).</li>
+ * <li>Applies HTTP Basic Auth using Mailgun's API key as the password and user "api".</li>
+ * </ul>
+ *
+ * @return a preconfigured {@link HttpClientBuilder} ready to build clients
+ * @throws SeCurisException if SSL initialization fails
+ */
private HttpClientBuilder createHttpClient() throws SeCurisException {
SSLContextBuilder builder = new SSLContextBuilder();
SSLConnectionSocketFactory sslsf = null;
try {
+ // Trust all X509 certificates (relies on HTTPS + Basic Auth; consider hardening in production).
builder.loadTrustMaterial((KeyStore) null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
@@ -82,30 +140,45 @@
LOG.error(e1);
throw new SeCurisException("Error creating SSL socket factory");
}
+
+ // Configure Basic Auth with Mailgun API key
CredentialsProvider provider = new BasicCredentialsProvider();
- UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("api", Config.get(Config.KEYS.MAILGUN_API_KEY));
+ UsernamePasswordCredentials credentials =
+ new UsernamePasswordCredentials("api", Config.get(Config.KEYS.MAILGUN_API_KEY));
provider.setCredentials(AuthScope.ANY, credentials);
- return HttpClientBuilder.create().setDefaultCredentialsProvider(provider).setSSLSocketFactory(sslsf);
+ return HttpClientBuilder.create()
+ .setDefaultCredentialsProvider(provider)
+ .setSSLSocketFactory(sslsf);
}
+ // ---------------------------------------------------------------------
+ // Email sending API
+ // ---------------------------------------------------------------------
+
/**
- * Basic method to send emails in text mode with attachment. The method is
- * synchronous, It waits until server responses.
- *
- * @param subject
- * @param body
- * @param to
- * @param file
- * @throws SeCurisException
- * @throws UnsupportedEncodingException
+ * sendEmail
+ * <p>
+ * Sends a plain-text email (UTF-8) via Mailgun. Optionally attaches a single file.
+ * This call is <b>synchronous</b> (blocking) and only returns once the HTTP response is received.
+ *
+ * @param subject Email subject (will be sent as UTF-8).
+ * @param body Email body in plain text (UTF-8).
+ * @param to Recipient address (required).
+ * @param cc Optional CC address, may be {@code null}.
+ * @param file Optional file to attach, may be {@code null}.
+ *
+ * @throws SeCurisServiceException if the HTTP call fails or Mailgun responds with a non-200 status
+ * @throws UnsupportedEncodingException kept for API compatibility (body/subject are forced to UTF-8)
*/
@SuppressWarnings("deprecation")
- public void sendEmail(String subject, String body, String to, String cc, File file) throws SeCurisServiceException, UnsupportedEncodingException {
+ public void sendEmail(String subject, String body, String to, String cc, File file)
+ throws SeCurisServiceException, UnsupportedEncodingException {
+
HttpPost postRequest = new HttpPost(serverUrl);
+ // Build multipart form body compatible with Mailgun
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
-
builder.setCharset(Charset.forName("utf-8"));
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
builder.addTextBody("from", Config.get(Config.KEYS.EMAIL_FROM_ADDRESS));
@@ -113,28 +186,34 @@
if (cc != null) {
builder.addTextBody("cc", cc);
}
- builder.addTextBody("subject", subject, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8")));
- builder.addTextBody("text", body, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8")));
- if (file != null) {
+ builder.addTextBody("subject",
+ subject, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8")));
+ builder.addTextBody("text",
+ body, ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), Charset.forName("utf-8")));
+ if (file != null) {
LOG.info("File to attach: {}", file.getAbsoluteFile());
builder.addPart("attachment", new FileBody(file));
}
postRequest.setEntity(builder.build());
+
+ // Execute HTTP request
HttpResponse response;
HttpClient httpClient = httpClientBuilder.build();
try {
response = httpClient.execute(postRequest);
+ // Mailgun returns JSON. We parse it to a Map for logging/validation.
String jsonLic = IOUtils.toString(response.getEntity().getContent());
if (response.getStatusLine().getStatusCode() == 200) {
LOG.debug("Response content read OK: {}", jsonLic);
Map<String, Object> responseBean = JsonUtils.json2map(jsonLic);
-
LOG.debug("Response mail read OK: {}", responseBean);
} else {
- throw new SeCurisServiceException(ErrorCodes.UNEXPECTED_ERROR, "Error sending email, response estatus: " + response.getStatusLine());
+ throw new SeCurisServiceException(
+ ErrorCodes.UNEXPECTED_ERROR,
+ "Error sending email, response estatus: " + response.getStatusLine());
}
} catch (IOException e) {
LOG.error("Error sending email", e);
@@ -143,21 +222,27 @@
}
/**
- * Basic method to send emails in text mode with attachment. The method is
- * asynchronous, It returns immediately
- *
- * @param subject
- * @param body
- * @param to
- * @param file
- * @throws SeCurisException
- * @throws UnsupportedEncodingException
+ * sendEmailAsync
+ * <p>
+ * Asynchronous variant of {@link #sendEmail(String, String, String, String, File)}.
+ * The call returns immediately and performs the HTTP request in a single-thread executor.
+ *
+ * @param subject Email subject (UTF-8).
+ * @param body Email body in plain text (UTF-8).
+ * @param to Recipient address.
+ * @param cc Optional CC address, may be {@code null}.
+ * @param file Optional attachment, may be {@code null}.
+ * @param callback Non-null callback to be notified on success or failure.
+ *
+ * @throws SeCurisException if there is a configuration or environment problem before dispatch
+ * @throws UnsupportedEncodingException for API compatibility (subject/body are encoded as UTF-8)
*/
- public void sendEmailAsync(String subject, String body, String to, String cc, File file, EmailCallback callback) throws SeCurisException,
- UnsupportedEncodingException {
+ public void sendEmailAsync(
+ String subject, String body, String to, String cc, File file, EmailCallback callback)
+ throws SeCurisException, UnsupportedEncodingException {
+
Executor ex = Executors.newSingleThreadExecutor();
ex.execute(new Runnable() {
-
@Override
public void run() {
try {
@@ -168,37 +253,61 @@
} catch (SeCurisServiceException e) {
callback.error(e);
}
-
}
});
-
}
+ // ---------------------------------------------------------------------
+ // Callback contract
+ // ---------------------------------------------------------------------
+
+ /**
+ * EmailCallback
+ * <p>
+ * Functional contract to be notified when an async send finishes.
+ */
public static interface EmailCallback {
+ /**
+ * success<p>
+ * Called when the email was sent and Mailgun returned HTTP 200.
+ */
public void success();
+ /**
+ * error<p>
+ * Called when there was a problem sending the email.
+ *
+ * @param e encapsulates the reason of failure
+ */
public void error(SeCurisServiceException e);
}
+ // ---------------------------------------------------------------------
+ // Manual test harness
+ // ---------------------------------------------------------------------
+
+ /**
+ * main<p>
+ * Simple manual test for the async email flow. Adjust addresses and file path before use.
+ *
+ * @param args program arguments (unused)
+ * @throws SeCurisException if configuration is invalid
+ * @throws UnsupportedEncodingException if UTF-8 encoding fails (unlikely)
+ */
public static void main(String[] args) throws SeCurisException, UnsupportedEncodingException {
- // new EmailManager().sendEmail("España así de bien",
- // "Me gusta esta prueba\nCon varias líneas\n\n\n--\nNo response",
- // "info@r75.es", new File(
- // "/Users/rob/Downloads/test.req"));
- new EmailManager().sendEmailAsync("España así de bien", "Me gusta esta prueba\nCon varias líneas\n\n\n--\nNo response", "info@r75.es",
- "dev@r75.es", new File("/Users/rob/Downloads/test.req"), new EmailCallback() {
+ // Example async call (subject/body contain non-ASCII content to validate UTF-8 handling).
+ new EmailManager().sendEmailAsync("España así de bien",
+ "Me gusta esta prueba\nCon varias líneas\n\n\n--\nNo response",
+ "info@r75.es",
+ "dev@r75.es",
+ new File("/Users/rob/Downloads/test.req"),
+ new EmailCallback() {
+ @Override
+ public void success() { LOG.info("Success!!!"); }
@Override
- public void success() {
- LOG.info("Success!!!");
- }
-
- @Override
- public void error(SeCurisServiceException e) {
- LOG.error("Error: {} !!!", e);
- }
+ public void error(SeCurisServiceException e) { LOG.error("Error: {} !!!", e); }
});
LOG.info("Waiting for email to be sent...");
}
-
}
--
Gitblit v1.3.2