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 &lt;roberto.sanchez@curisit.net&gt;
+ * 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