UnboundID Server SDK

Ping Identity
UnboundID Server SDK Documentation

ExampleAlertHandler.java

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * docs/licenses/cddl.txt
 * or http://www.opensource.org/licenses/cddl1.php.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * docs/licenses/cddl.txt.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2010-2023 Ping Identity Corporation
 */
package com.unboundid.directory.sdk.examples;



import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.unboundid.directory.sdk.common.api.AlertHandler;
import com.unboundid.directory.sdk.common.config.AlertHandlerConfig;
import com.unboundid.directory.sdk.common.types.AlertNotification;
import com.unboundid.directory.sdk.common.types.EMailAttachment;
import com.unboundid.directory.sdk.common.types.EMailMessage;
import com.unboundid.directory.sdk.common.types.LogSeverity;
import com.unboundid.directory.sdk.common.types.ServerContext;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.FileArgument;
import com.unboundid.util.args.StringArgument;



/**
 * This class provides a simple example of an alert handler that can send an
 * email message to a specified set of recipients whenever an administrative
 * alert is generated within the server.  It offers the following configuration
 * arguments:
 * <UL>
 *   <LI>recipient -- An email address to which the message should be sent.
 *       This is a required argument that must be provided at least once, but
 *       may be given multiple times to specify multiple recipients.</LI>
 *   <LI>sender -- The email address from which the message should be sent.
 *       This is a required argument that must be provided exactly once.</LI>
 *   <LI>reply-to -- The email address to which replies should be sent by
 *       default.  This is an optional argument that may be provided at most
 *       once.</LI>
 *   <LI>subject -- The subject for the email message.  This is an optional
 *       argument that may be provided at most once.  If it is not given, then
 *       a subject will be generated from the alert contents.</LI>
 * </UL>
 */
public final class ExampleAlertHandler
       extends AlertHandler
{
  /**
   * The name of the hidden argument that will be used to specify the output
   * directory to use for testing.
   */
  private static final String ARG_NAME_OUTPUT_DIRECTORY = "output-directory";



  /**
   * The name of the argument that will be used to specify the email address of
   * a recipient.
   */
  private static final String ARG_NAME_RECIPIENT = "recipient";



  /**
   * The name of the argument that will be used to specify the reply-to email
   * address.
   */
  private static final String ARG_NAME_REPLY_TO = "reply-to";



  /**
   * The name of the argument that will be used to specify the sender email
   * address.
   */
  private static final String ARG_NAME_SENDER = "sender";



  /**
   * The name of the argument that will be used to specify the message subject.
   */
  private static final String ARG_NAME_SUBJECT = "subject";



  // The configuration for this alert handler.
  private AlertHandlerConfig config;

  // The output directory to which the email messages shoudl be written.
  private volatile File outputDirectory;

  // The server context for the server in which this extension is running.
  private ServerContext serverContext;

  // The addresses to which the message should be sent.
  private volatile Set<String> recipientAddresses;

  // The reply-to address for the email message.
  private volatile String replyToAddress;

  // The sender address for the email message.
  private volatile String senderAddress;

  // The subject for the email message.
  private volatile String subject;



  /**
   * Creates a new instance of this alert handler.  All alert handler
   * implementations must include a default constructor, but any initialization
   * should generally be done in the {@code initializeAlertHandler} method.
   */
  public ExampleAlertHandler()
  {
    config = null;
    outputDirectory = null;
    recipientAddresses = Collections.emptySet();
    senderAddress = null;
    replyToAddress = null;
    subject = null;
  }



  /**
   * Retrieves a human-readable name for this extension.
   *
   * @return  A human-readable name for this extension.
   */
  @Override()
  public String getExtensionName()
  {
    return "Example Alert Handler";
  }



  /**
   * Retrieves a human-readable description for this extension.  Each element
   * of the array that is returned will be considered a separate paragraph in
   * generated documentation.
   *
   * @return  A human-readable description for this extension, or {@code null}
   *          or an empty array if no description should be available.
   */
  @Override()
  public String[] getExtensionDescription()
  {
    return new String[]
    {
      "This alert handler serves an example that may be used to demonstrate " +
           "the process for creating a third-party alert handler.  It will " +
           "send an email message for each alert generated within the " +
           "server."
    };
  }



  /**
   * Updates the provided argument parser to define any configuration arguments
   * which may be used by this alert handler.  The argument parser may also be
   * updated to define relationships between arguments (e.g., to specify
   * required, exclusive, or dependent argument sets).
   *
   * @param  parser  The argument parser to be updated with the configuration
   *                 arguments which may be used by this alert handler.
   *
   * @throws  ArgumentException  If a problem is encountered while updating the
   *                             provided argument parser.
   */
  @Override()
  public void defineConfigArguments(final ArgumentParser parser)
         throws ArgumentException
  {
    // Add an argument that specifies the recipient addresses.
    Character shortIdentifier = null;
    String    longIdentifier  = ARG_NAME_RECIPIENT;
    boolean   required        = true;
    int       maxOccurrences  = 0;
    String    placeholder     = "{address}";
    String    description     = "An email address to which the message " +
         "should be sent.  This must be provided at least once, but may be " +
         "given multiple times to specify multiple recipients.";

    parser.addArgument(new StringArgument(shortIdentifier, longIdentifier,
         required, maxOccurrences, placeholder, description));


    // Add an argument that specifies the sender addresses.
    shortIdentifier = null;
    longIdentifier  = ARG_NAME_SENDER;
    required        = true;
    maxOccurrences  = 1;
    placeholder     = "{address}";
    description     = "The email address from which the message should be " +
         "sent.  This must be provided exactly once.";

    parser.addArgument(new StringArgument(shortIdentifier, longIdentifier,
         required, maxOccurrences, placeholder, description));


    // Add an argument that specifies the reply-to addresses.
    shortIdentifier = null;
    longIdentifier  = ARG_NAME_REPLY_TO;
    required        = false;
    maxOccurrences  = 1;
    placeholder     = "{address}";
    description     = "The email address to which replies should be sent by " +
         "default.  This is optional and may be provided at most once.";

    parser.addArgument(new StringArgument(shortIdentifier, longIdentifier,
         required, maxOccurrences, placeholder, description));


    // Add an argument that specifies the subject.
    shortIdentifier = null;
    longIdentifier  = ARG_NAME_SUBJECT;
    required        = false;
    maxOccurrences  = 1;
    placeholder     = "{subject}";
    description     = "The subject for the email message.  This is optional " +
         "and may be provided at most once.  If this is not provided, a " +
         "subject will be generated from the alert contents.";

    parser.addArgument(new StringArgument(shortIdentifier, longIdentifier,
         required, maxOccurrences, placeholder, description));


    // Add a hidden argument that specifies an output directory to which
    // messages should be written instead of being sent.
    shortIdentifier = null;
    longIdentifier  = ARG_NAME_OUTPUT_DIRECTORY;
    required        = false;
    maxOccurrences  = 1;
    placeholder     = "{path}";
    description     = "The path to a directory to which a string " +
         "representation of the email messages should be written instead " +
         "of attempting to send them.  This is only intended for testing " +
         "purposes.";

    final FileArgument outputDirArg = new FileArgument(shortIdentifier,
         longIdentifier, required, maxOccurrences, placeholder, description,
         true, true, false, true);
    outputDirArg.setHidden(true);
    parser.addArgument(outputDirArg);
  }



  /**
   * Initializes this alert handler.
   *
   * @param  serverContext  A handle to the server context for the server in
   *                        which this extension is running.
   * @param  config         The general configuration for this alert handler.
   * @param  parser         The argument parser which has been initialized from
   *                        the configuration for this alert handler.
   *
   * @throws  LDAPException  If a problem occurs while initializing this alert
   *                         handler.
   */
  @Override()
  public void initializeAlertHandler(final ServerContext serverContext,
                                     final AlertHandlerConfig config,
                                     final ArgumentParser parser)
         throws LDAPException
  {
    serverContext.debugInfo("Beginning alert handler initialization");

    this.config = config;
    this.serverContext = serverContext;

    applyConfiguration(parser);
  }



  /**
   * Indicates whether the configuration contained in the provided argument
   * parser represents a valid configuration for this extension.
   *
   * @param  config               The general configuration for this alert
   *                              handler.
   * @param  parser               The argument parser which has been initialized
   *                              with the proposed configuration.
   * @param  unacceptableReasons  A list that can be updated with reasons that
   *                              the proposed configuration is not acceptable.
   *
   * @return  {@code true} if the proposed configuration is acceptable, or
   *          {@code false} if not.
   */
  @Override()
  public boolean isConfigurationAcceptable(final AlertHandlerConfig config,
                      final ArgumentParser parser,
                      final List<String> unacceptableReasons)
  {
    this.config = config;
    this.serverContext = config.getServerContext();

    // No special validation is required.
    return true;
  }



  /**
   * Attempts to apply the configuration contained in the provided argument
   * parser.
   *
   * @param  config                The general configuration for this alert
   *                               handler.
   * @param  parser                The argument parser which has been
   *                               initialized with the new configuration.
   * @param  adminActionsRequired  A list that can be updated with information
   *                               about any administrative actions that may be
   *                               required before one or more of the
   *                               configuration changes will be applied.
   * @param  messages              A list that can be updated with information
   *                               about the result of applying the new
   *                               configuration.
   *
   * @return  A result code that provides information about the result of
   *          attempting to apply the configuration change.
   */
  @Override()
  public ResultCode applyConfiguration(final AlertHandlerConfig config,
                                       final ArgumentParser parser,
                                       final List<String> adminActionsRequired,
                                       final List<String> messages)
  {
    this.config = config;
    this.serverContext = config.getServerContext();

    applyConfiguration(parser);
    return ResultCode.SUCCESS;
  }



  /**
   * Applies the configuration from the provided argument parser to this alert
   * handler.
   *
   * @param  parser  The argument parser which has been initialized with the
   *                 configuration to be applied.
   */
  private void applyConfiguration(final ArgumentParser parser)
  {
    recipientAddresses = Collections.unmodifiableSet(
         new LinkedHashSet<>(parser.getStringArgument(ARG_NAME_RECIPIENT).
              getValues()));
    senderAddress = parser.getStringArgument(ARG_NAME_SENDER).getValue();
    replyToAddress = parser.getStringArgument(ARG_NAME_REPLY_TO).getValue();
    subject = parser.getStringArgument(ARG_NAME_SUBJECT).getValue();
    outputDirectory =
         parser.getFileArgument(ARG_NAME_OUTPUT_DIRECTORY).getValue();
  }



  /**
   * Performs any cleanup which may be necessary when this alert handler is
   * to be taken out of service.
   */
  @Override()
  public void finalizeAlertHandler()
  {
    // No implementation is required.
  }



  /**
   * Performs any processing which may be necessary to handle the provided alert
   * notification.
   *
   * @param  alert  The alert notification generated within the server.
   */
  @Override()
  public void handleAlert(final AlertNotification alert)
  {
    // Get the header information.
    final Set<String> toAddresses = recipientAddresses;
    final String sender = senderAddress;
    final String replyTo = replyToAddress;

    final String messageSubject;
    if (subject == null)
    {
      messageSubject = "Administrative Alert:  " + alert.getAlertTypeName();
    }
    else
    {
      messageSubject = subject;
    }


    // Construct the plain-text body for the message.
    final List<String> plainTextMessageLines = new ArrayList<>();
    plainTextMessageLines.add("The " + serverContext.getCompactProductName() +
              " has generated an administrative alert with the following " +
              "information:");
    plainTextMessageLines.add("");
    plainTextMessageLines.add("Alert ID: " + alert.getAlertID());
    plainTextMessageLines.add("Alert Type: " + alert.getAlertTypeName());
    plainTextMessageLines.add("Alert Severity: " +
              alert.getAlertSeverity().getName());
    plainTextMessageLines.add("Generated by Class: " +
              alert.getAlertGeneratorClassName());
    plainTextMessageLines.add("Alert Message: " + alert.getAlertMessage());

    if (! alert.getAdditionalInformation().isEmpty())
    {
      plainTextMessageLines.add("");
      plainTextMessageLines.add("Additional Alert Information:");
      for (final Map.Entry<Object,Object> e :
           alert.getAdditionalInformation().entrySet())
      {
        plainTextMessageLines.add("* " + e.getKey() + ": " + e.getValue());
      }
    }

    final String plainTextBody =
         StaticUtils.linesToString(plainTextMessageLines);


    // Construct the HTML body for the message.
    final List<String> htmlMessageLines = new ArrayList<>();
    htmlMessageLines.add("<html>");
    htmlMessageLines.add("  <body>");
    htmlMessageLines.add("    <p>");
    htmlMessageLines.add("      The " + serverContext.getCompactProductName() +
         " has generated an administrative alert with the following " +
         "information:");
    htmlMessageLines.add("    </p>");
    htmlMessageLines.add("");
    htmlMessageLines.add("    <ul>");
    htmlMessageLines.add("      <li>Alert ID: " + alert.getAlertID() + "</li>");
    htmlMessageLines.add("      <li>Alert Type: " + alert.getAlertTypeName() +
         "</li>");
    htmlMessageLines.add("      <li>Alert Severity: " +
              alert.getAlertSeverity().getName() + "</li>");
    htmlMessageLines.add("      <li>Generated by Class: " +
              alert.getAlertGeneratorClassName() + "</li>");
    htmlMessageLines.add("      <li>Alert Message: " +
         alert.getAlertMessage() + "</li>");

    if (! alert.getAdditionalInformation().isEmpty())
    {
      htmlMessageLines.add("      <li>");
      htmlMessageLines.add("        Additional Alert Information:");
      htmlMessageLines.add("        <ul>");

      for (final Map.Entry<Object,Object> e :
           alert.getAdditionalInformation().entrySet())
      {
        htmlMessageLines.add("          <li>" + e.getKey() + ":  " +
             e.getValue() + "</li>");
      }

      htmlMessageLines.add("        </ul>");
      htmlMessageLines.add("      </li>");
    }

    htmlMessageLines.add("    </ul>");
    htmlMessageLines.add("  </body>");
    htmlMessageLines.add("</html>");

    final String htmlBody = StaticUtils.linesToString(htmlMessageLines);


    // Generate and send the email message.
    final Set<String> ccAddresses = Collections.emptySet();
    final Set<String> bccAddresses = Collections.emptySet();
    final Map<String,List<String>> customHeaders = Collections.emptyMap();
    final List<EMailAttachment> attachments = Collections.emptyList();

    try
    {
      final EMailMessage message = serverContext.createEMailMessage(sender,
           replyTo, toAddresses, ccAddresses, bccAddresses, messageSubject,
           plainTextBody, htmlBody, customHeaders, attachments);
      if (outputDirectory == null)
      {
        message.send();
      }
      else
      {
        final File f = new File(outputDirectory, alert.getAlertID() + ".json");
        try (PrintWriter w = new PrintWriter(f))
        {
          w.println(message.toString());
        }
      }
    }
    catch (final Exception e)
    {
      serverContext.debugCaught(e);
      serverContext.logMessage(LogSeverity.SEVERE_ERROR,
           "The example alert handler defined in config entry '" +
                config.getConfigObjectDN() + "' was unable to send an email " +
                "message in response to administrative alert " + alert +
                ":  " + StaticUtils.getExceptionMessage(e));
    }
  }



  /**
   * Retrieves a map containing examples of configurations that may be used for
   * this extension.  The map key should be a list of sample arguments, and the
   * corresponding value should be a description of the behavior that will be
   * exhibited by the extension when used with that configuration.
   *
   * @return  A map containing examples of configurations that may be used for
   *          this extension.  It may be {@code null} or empty if there should
   *          not be any example argument sets.
   */
  @Override()
  public Map<List<String>,String> getExamplesArgumentSets()
  {
    final LinkedHashMap<List<String>,String> exampleMap =
         new LinkedHashMap<>(1);

    exampleMap.put(
         Arrays.asList(
              ARG_NAME_RECIPIENT + "=directoryadmins@example.com",
              ARG_NAME_SENDER + "=adminalerts@example.com"),
         "Send an email message to directoryadmins@example.com from " +
              "adminalerts@example.com for each administrative alert.  The " +
              "message will have a subject generated from the alert content.");

    return exampleMap;
  }
}