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