/* * 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; } }