/* * 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.groovy; import java.io.File; import java.io.PrintWriter; import java.math.BigInteger; import java.util.Date; import java.util.List; import java.util.Set; import com.unboundid.directory.sdk.ds.config.ChangeSubscriptionHandlerConfig; import com.unboundid.directory.sdk.common.operation.AddRequest; import com.unboundid.directory.sdk.common.operation.AddResult; import com.unboundid.directory.sdk.common.operation.DeleteRequest; import com.unboundid.directory.sdk.common.operation.DeleteResult; import com.unboundid.directory.sdk.common.operation.ModifyRequest; import com.unboundid.directory.sdk.common.operation.ModifyResult; import com.unboundid.directory.sdk.common.operation.ModifyDNRequest; import com.unboundid.directory.sdk.common.operation.ModifyDNResult; import com.unboundid.directory.sdk.common.types.AlertSeverity; import com.unboundid.directory.sdk.common.types.CompletedOperationContext; import com.unboundid.directory.sdk.common.types.Entry; import com.unboundid.directory.sdk.ds.scripting.ScriptedChangeSubscriptionHandler; import com.unboundid.directory.sdk.ds.types.ChangeSubscription; import com.unboundid.directory.sdk.ds.types.DirectoryServerContext; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.ldif.LDIFChangeRecord; import com.unboundid.util.StaticUtils; import com.unboundid.util.args.ArgumentException; import com.unboundid.util.args.ArgumentParser; import com.unboundid.util.args.FileArgument; /** * This class provides a simple example of a scripted change subscription * handler which will write information about each matching change to a file in * a specified directory. The file will be named with the change sequence * number. It has one configuration argument: * <UL> * <LI>log-directory -- The path to the directory in which files will be * written about matching changes.</LI> * </UL> */ public final class ExampleScriptedChangeSubscriptionHandler extends ScriptedChangeSubscriptionHandler { /** * The name of the argument that will be used to specify the path to the * directory in which to write log files. */ private static final String ARG_NAME_LOG_DIR = "log-directory"; // The server context for the server in which this extension is running. private DirectoryServerContext serverContext; // The directory in which to write log files. private volatile File logDirectory; /** * Creates a new instance of this change subscription handler. All change * subscription handler implementations must include a default constructor, * but any initialization should generally be done in the * {@code initializeChangeSubscriptionHandler} method. */ public ExampleScriptedChangeSubscriptionHandler() { // No implementation required. } /** * Updates the provided argument parser to define any configuration arguments * which may be used by this change subscription 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 change subscription * 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 allows you to specify the log directory. Character shortIdentifier = null; String longIdentifier = ARG_NAME_LOG_DIR; boolean required = true; int maxOccurrences = 1; String placeholder = "{path}"; String description = "The path to the directory in which log " + "files should be written with information about matching changes. " + "Relative paths will be relative to the server root."; boolean fileMustExist = true; boolean parentMustExist = true; boolean mustBeFile = false; boolean mustBeDirectory = true; parser.addArgument(new FileArgument(shortIdentifier, longIdentifier, required, maxOccurrences, placeholder, description, fileMustExist, parentMustExist, mustBeFile, mustBeDirectory)); } /** * Initializes this change subscription 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 change * subscription handler. * @param parser The argument parser which has been initialized from * the configuration for this change subscription * handler. * * @throws LDAPException If a problem occurs while initializing this change * subscription handler. */ @Override() public void initializeChangeSubscriptionHandler( final DirectoryServerContext serverContext, final ChangeSubscriptionHandlerConfig config, final ArgumentParser parser) throws LDAPException { serverContext.debugInfo( "Beginning change subscription handler initialization"); this.serverContext = serverContext; // Get the path to the directory in which the log files should be written. final FileArgument logDirArg = (FileArgument) parser.getNamedArgument(ARG_NAME_LOG_DIR); logDirectory = logDirArg.getValue(); } /** * Indicates whether the configuration contained in the provided argument * parser represents a valid configuration for this extension. * * @param config The general configuration for this change * subscription 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 ChangeSubscriptionHandlerConfig config, final ArgumentParser parser, final List<String> unacceptableReasons) { // 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 change * subscription 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 ChangeSubscriptionHandlerConfig config, final ArgumentParser parser, final List<String> adminActionsRequired, final List<String> messages) { // Get the new path to the log directory. We don't really care if it's the // the same as the directory that was already in use. final FileArgument logDirArg = (FileArgument) parser.getNamedArgument(ARG_NAME_LOG_DIR); logDirectory = logDirArg.getValue(); return ResultCode.SUCCESS; } /** * Performs any cleanup which may be necessary when this change subscription * handler is to be taken out of service. */ @Override() public void finalizeChangeSubscriptionHandler() { // No finalization is required. } /** * Performs any processing necessary for an add operation matching the * subscription criteria. * * @param operationContext The context for the add operation. * @param sequenceNumber The sequence number for the change * subscription notification. * @param changeSubscriptions The set of change subscriptions whose criteria * matched the add operation. * @param addRequest Information about the request for the add * operation that was processed. * @param addResult Information about the result for the add * operation that was processed. * @param entry The entry that was added to the server. */ @Override() public void addOperationProcessed( final CompletedOperationContext operationContext, final BigInteger sequenceNumber, final Set<ChangeSubscription> changeSubscriptions, final AddRequest addRequest, final AddResult addResult, final Entry entry) { writeChange(operationContext, sequenceNumber, changeSubscriptions, addRequest.toLDIFChangeRecord()); } /** * Performs any processing necessary for a delete operation matching the * subscription criteria. * * @param operationContext The context for the delete operation. * @param sequenceNumber The sequence number for the change * subscription notification. * @param changeSubscriptions The set of change subscriptions whose criteria * matched the delete operation. * @param deleteRequest Information about the request for the delete * operation that was processed. * @param deleteResult Information about the result for the delete * operation that was processed. * @param entry The entry that was removed from the server. */ @Override() public void deleteOperationProcessed( final CompletedOperationContext operationContext, final BigInteger sequenceNumber, final Set<ChangeSubscription> changeSubscriptions, final DeleteRequest deleteRequest, final DeleteResult deleteResult, final Entry entry) { writeChange(operationContext, sequenceNumber, changeSubscriptions, deleteRequest.toLDIFChangeRecord()); } /** * Performs any processing necessary for a modify operation matching the * subscription criteria. * * @param operationContext The context for the modify operation. * @param sequenceNumber The sequence number for the change * subscription notification. * @param changeSubscriptions The set of change subscriptions whose criteria * matched the modify operation. * @param modifyRequest Information about the request for the modify * operation that was processed. * @param modifyResult Information about the result for the modify * operation that was processed. * @param oldEntry The entry as it appeared before the changes * were applied. * @param newEntry The entry as it appeared immediately after the * changes were applied. */ @Override() public void modifyOperationProcessed( final CompletedOperationContext operationContext, final BigInteger sequenceNumber, final Set<ChangeSubscription> changeSubscriptions, final ModifyRequest modifyRequest, final ModifyResult modifyResult, final Entry oldEntry, final Entry newEntry) { writeChange(operationContext, sequenceNumber, changeSubscriptions, modifyRequest.toLDIFChangeRecord()); } /** * Performs any processing necessary for a modify DN operation matching the * subscription criteria. * * @param operationContext The context for the modify DN operation. * @param sequenceNumber The sequence number for the change * subscription notification. * @param changeSubscriptions The set of change subscriptions whose criteria * matched the modify DN operation. * @param modifyDNRequest Information about the request for the modify * DN operation that was processed. * @param modifyDNResult Information about the result for the modify DN * operation that was processed. * @param oldEntry The entry as it appeared before being renamed. * @param newEntry The entry as it appeared immediately after * being renamed. */ @Override() public void modifyDNOperationProcessed( final CompletedOperationContext operationContext, final BigInteger sequenceNumber, final Set<ChangeSubscription> changeSubscriptions, final ModifyDNRequest modifyDNRequest, final ModifyDNResult modifyDNResult, final Entry oldEntry, final Entry newEntry) { writeChange(operationContext, sequenceNumber, changeSubscriptions, modifyDNRequest.toLDIFChangeRecord()); } /** * Writes information about the change to a file in the configured log * directory. * * @param operationContext The context for the associated operation. * @param sequenceNumber The sequence number for the change * subscription notification. * @param changeSubscriptions The set of change subscriptions whose criteria * matched the associated operation. * @param changeRecord The change record that was processed. */ private void writeChange(final CompletedOperationContext operationContext, final BigInteger sequenceNumber, final Set<ChangeSubscription> changeSubscriptions, final LDIFChangeRecord changeRecord) { // Determine the file that should be used for the change. It will be named // based on the sequence number, which is guaranteed to be unique (even // across restarts). final File ldifFile = new File(logDirectory, sequenceNumber.toString() + ".ldif"); // Open the file for writing. final PrintWriter w; try { w = new PrintWriter(ldifFile); } catch (final Exception e) { // About the only thing we can do here is to generate an alert to notify // administrators of the problem. serverContext.debugCaught(e); serverContext.sendAlert(AlertSeverity.ERROR, "Unable to write a record of change " + changeRecord.toString() + " to file " + ldifFile.getAbsolutePath() + ": " + StaticUtils.getExceptionMessage(e)); return; } // Write the header to the file. Write it using LDIF comments so that the // change can be parsed using an LDIF reader. w.println("# Connection ID: " + operationContext.getConnectionID()); w.println("# Operation ID: " + operationContext.getOperationID()); w.println("# Requester DN: " + operationContext.getAuthorizationDN()); w.println("# Request Time: " + new Date(operationContext.getProcessingStartTime())); w.println("# Processing Time (ns): " + new Date(operationContext.getProcessingTimeNanos())); for (final ChangeSubscription s : changeSubscriptions) { w.println("# Matches change subscription: " + s.getName()); } // Write an LDIF representation of the change and close the file. w.println(changeRecord.toLDIFString()); w.close(); } }