UnboundID Server SDK

Ping Identity
UnboundID Server SDK Documentation

ExampleTask.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.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.unboundid.directory.sdk.common.types.InternalConnection;
import com.unboundid.directory.sdk.common.types.LogSeverity;
import com.unboundid.directory.sdk.ds.api.Task;
import com.unboundid.directory.sdk.ds.types.DirectoryServerContext;
import com.unboundid.directory.sdk.ds.types.TaskContext;
import com.unboundid.directory.sdk.ds.types.TaskReturnState;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPSearchException;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.ldap.sdk.SearchResultListener;
import com.unboundid.ldap.sdk.SearchResultReference;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldif.LDIFWriter;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.DNArgument;
import com.unboundid.util.args.FileArgument;
import com.unboundid.util.args.FilterArgument;
import com.unboundid.util.args.StringArgument;



/**
 * This class provides a simple example of a task that will perform an internal
 * search and write the results to a file on the server filesystem.  The search
 * criteria will be specified using the provided arguments:
 * <UL>
 *   <LI>base-dn -- The base DN for the search to perform.</LI>
 *   <LI>scope -- The scope for the search.  The value should be one of the
 *       following strings:  base, one, sub, or subordinate-subtree.</LI>
 *   <LI>filter -- The filter for the search.</LI>
 *   <LI>attribute -- An attribute to request.  This may be provided multiple
 *       times to request multiple attributes.</LI>
 *   <LI>output-file -- The path to the file to which the search results should
 *       be written.</LI>
 * </UL>
 */
public final class ExampleTask
       extends Task
       implements SearchResultListener
{
  /**
   * The name of the argument used to specify the search base DN.
   */
  private static final String ARG_NAME_BASE_DN = "base-dn";



  /**
   * The name of the argument used to specify the search scope.
   */
  private static final String ARG_NAME_SCOPE = "scope";



  /**
   * The name of the argument used to specify the filter.
   */
  private static final String ARG_NAME_FILTER = "filter";



  /**
   * The name of the argument used to specify the requested attributes.
   */
  private static final String ARG_NAME_ATTR = "attribute";



  /**
   * The name of the argument used to specify the path to the output file.
   */
  private static final String ARG_NAME_OUTPUT_FILE = "output-file";



  /**
   * The set of allowed search scope values.
   */
  private static final Set<String> ALLOWED_SCOPES;



  /**
   * The map of search scope names to values.
   */
  private static final Map<String,SearchScope> SCOPE_MAP;



  static
  {
    final LinkedHashMap<String,SearchScope> scopeMap =
         new LinkedHashMap<String,SearchScope>(4);
    scopeMap.put("base", SearchScope.BASE);
    scopeMap.put("one", SearchScope.ONE);
    scopeMap.put("sub", SearchScope.SUB);
    scopeMap.put("subordinate-subtree", SearchScope.SUBORDINATE_SUBTREE);

    SCOPE_MAP = Collections.unmodifiableMap(scopeMap);
    ALLOWED_SCOPES = Collections.unmodifiableSet(scopeMap.keySet());
  }



  /**
   * The serial version UID for this serializable class.
   */
  private static final long serialVersionUID = 9088385267966197988L;



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

  // The output file for the search results.
  private File outputFile;

  // The filter for the search.
  private Filter filter;

  // The LDIF writer to use when writing the results.
  private volatile LDIFWriter ldifWriter;

  // The list of requested attributes.
  private List<String> requestedAttributes;

  // The scope for the search.
  private SearchScope scope;

  // The base DN for the search.
  private String baseDN;

  // The task context for the task.
  private volatile TaskContext taskContext;

  // The return state for the task.
  private volatile TaskReturnState returnState;



  /**
   * Creates a new instance of this task.  All task implementations must
   * include a default constructor, but any initialization should generally be
   * done in the {@code initializeTask} method.
   */
  public ExampleTask()
  {
    // No implementation required.
  }



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



  /**
   * 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 task serves an example that may be used to demonstrate the " +
           "process for creating a third-party task.  It will simply " +
           "perform an internal search (in which the base DN, scope, filter, " +
           "and requested attributes are provided as task arguments) and " +
           "write the results to a file on the server filesystem."
    };
  }



  /**
   * Updates the provided argument parser to define any configuration arguments
   * which may be used by this plugin.  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 plugin.
   *
   * @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 search base DN.
    Character shortIdentifier = null;
    String    longIdentifier  = ARG_NAME_BASE_DN;
    boolean   required        = true;
    int       maxOccurrences  = 1;
    String    placeholder     = "{dn}";
    String    description     = "The base DN to use for the search.";

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


    // Add an argument that allows you to specify the search scope.
    shortIdentifier = null;
    longIdentifier  = ARG_NAME_SCOPE;
    required        = true;
    maxOccurrences  = 1;
    placeholder     = "{base|one|sub|subordinate-subtree}";
    description     = "The scope to use for the search.";

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


    // Add an argument that allows you to specify the search filter.
    shortIdentifier = null;
    longIdentifier  = ARG_NAME_FILTER;
    required        = true;
    maxOccurrences  = 1;
    placeholder     = "{filter}";
    description     = "The filter to use for the search.";

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


    // Add an argument that allows you to specify the requested attributes.
    shortIdentifier = null;
    longIdentifier  = ARG_NAME_ATTR;
    required        = false;
    maxOccurrences  = 0; // No limit
    placeholder     = "{attr}";
    description     = "An attribute to include in matching entries.  This " +
         "argument may be provided multiple times to request multiple " +
         "attributes.  If no requested attributes are provided, then all " +
         "user attributes will be requested.";

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


    // Add an argument that allows you to specify the output file.
    shortIdentifier = null;
    longIdentifier  = ARG_NAME_OUTPUT_FILE;
    required        = true;
    maxOccurrences  = 1;
    placeholder     = "{path}";
    description     = "The output file to which the search results should be " +
         "written.  Relative paths will be relative to the server root.";

    boolean fileMustExist   = false;
    boolean parentMustExist = true;
    boolean mustBeFile      = true;
    boolean mustBeDirectory = false;

    parser.addArgument(new FileArgument(shortIdentifier, longIdentifier,
         required, maxOccurrences, placeholder, description, fileMustExist,
         parentMustExist, mustBeFile, mustBeDirectory));
  }



  /**
   * Initializes this task.
   *
   * @param  serverContext  A handle to the server context for the server in
   *                        which this task is running.
   * @param  parser         The argument parser which has been initialized from
   *                        the configuration for this task.
   *
   * @throws  LDAPException  If a problem occurs while initializing this task.
   */
  @Override()
  public void initializeTask(final DirectoryServerContext serverContext,
                             final ArgumentParser parser)
         throws LDAPException
  {
    this.serverContext = serverContext;


    // Get the base DN.
    final DNArgument baseArg =
         (DNArgument) parser.getNamedArgument(ARG_NAME_BASE_DN);
    baseDN = baseArg.getValue().toString();


    // Get the scope.
    final StringArgument scopeArg =
         (StringArgument) parser.getNamedArgument(ARG_NAME_SCOPE);
    scope = SCOPE_MAP.get(StaticUtils.toLowerCase(scopeArg.getValue()));


    // Get the filter.
    final FilterArgument filterArg =
         (FilterArgument) parser.getNamedArgument(ARG_NAME_FILTER);
    filter = filterArg.getValue();


    // Get the list of requested attributes.
    final StringArgument attrsArg =
         (StringArgument) parser.getNamedArgument(ARG_NAME_ATTR);
    requestedAttributes = attrsArg.getValues();


    // Get the output file.
    final FileArgument outputFileArg =
         (FileArgument) parser.getNamedArgument(ARG_NAME_OUTPUT_FILE);
    outputFile = outputFileArg.getValue();
  }



  /**
   * Retrieves a human-readable name that may be used for this task.
   *
   * @return  A human-readable name that may be used for this task.
   */
  @Override()
  public String getTaskName()
  {
    return "Example Internal Search Task";
  }



  /**
   * Performs the appropriate processing for this task.
   *
   * @param  taskContext  Information about the task to be run.
   *
   * @return  Information about the state of the task after processing has
   *          completed.
   */
  @Override()
  public TaskReturnState runTask(final TaskContext taskContext)
  {
    this.taskContext = taskContext;

    // Create an LDIF writer to use to write the results.
    try
    {
      ldifWriter = new LDIFWriter(outputFile);
    }
    catch (final Exception e)
    {
      serverContext.debugCaught(e);
      serverContext.logMessage(LogSeverity.SEVERE_ERROR,
           "Unable to create an LDIF writer to write to file " +
                outputFile.getAbsolutePath() + " for task " +
                taskContext.getTaskEntryDN() + ":  " +
                StaticUtils.getExceptionMessage(e));
      return TaskReturnState.STOPPED_BY_ERROR;
    }

    try
    {
      // Append a header to the LDIF file with details of the search.
      try
      {
        ldifWriter.writeComment("Internal search initiated at " +
                  new Date().toString() + " for task " +
                  taskContext.getTaskEntryDN(),
             false, false);
        ldifWriter.writeComment("Base DN:  " + baseDN, false, false);
        ldifWriter.writeComment("Scope:  " + scope.getName(), false, false);
        ldifWriter.writeComment("Filter:  " + filter.toString(), false, false);
        ldifWriter.writeComment("Requested Attributes:  " + requestedAttributes,
             false, true);
      }
      catch (final Exception e)
      {
        serverContext.debugCaught(e);
        serverContext.logMessage(LogSeverity.SEVERE_ERROR,
             "Unable to write a header to LDIF file " +
                  outputFile.getAbsolutePath() + ":  " +
                  StaticUtils.getExceptionMessage(e));
        return TaskReturnState.STOPPED_BY_ERROR;
      }


      // Create the search request to issue.  We'll use a search result listener
      // since the search may return a large number of entries.
      final SearchRequest searchRequest = new SearchRequest(this, baseDN, scope,
           filter);
      searchRequest.setAttributes(requestedAttributes);


      // Get an internal connection and issue the search.
      returnState = TaskReturnState.COMPLETED_SUCCESSFULLY;
      SearchResult searchResult;
      try
      {
        final InternalConnection conn =
             serverContext.getInternalRootConnection();
        searchResult = conn.search(searchRequest);
      }
      catch (final LDAPSearchException lse)
      {
        serverContext.debugCaught(lse);

        searchResult = lse.getSearchResult();
        if (returnState == TaskReturnState.COMPLETED_SUCCESSFULLY)
        {
          returnState = TaskReturnState.COMPLETED_WITH_ERRORS;
        }

        serverContext.logMessage(LogSeverity.MILD_ERROR,
             "An error occurred while processing the search for task " +
                  taskContext.getTaskEntryDN() + ":  " +
                  searchResult.toString());
      }


      // Append a footer to the LDIF file with a summary of the results.
      try
      {
        ldifWriter.writeComment("Internal search completed at " +
                  new Date().toString(),
             true, false);
        ldifWriter.writeComment("Result Code:  " +
                  searchResult.getResultCode().toString(),
             false, false);

        final String diagnosticMessage = searchResult.getDiagnosticMessage();
        if (diagnosticMessage != null)
        {
          ldifWriter.writeComment("Diagnostic Message:  " + diagnosticMessage,
               false, false);
        }

        final String matchedDN = searchResult.getMatchedDN();
        if (matchedDN != null)
        {
          ldifWriter.writeComment("Matched DN:  " + matchedDN,
               false, false);
        }

        final String[] referralURLs = searchResult.getReferralURLs();
        if ((referralURLs != null) && (referralURLs.length > 0))
        {
          ldifWriter.writeComment("Referral URLs:  " +
                    Arrays.toString(referralURLs),
               false, false);
        }

        ldifWriter.writeComment("Entries Returned:  " +
                  searchResult.getEntryCount(),
             false, false);
        ldifWriter.writeComment("References Returned:  " +
                  searchResult.getReferenceCount(),
             false, false);
      }
      catch (final Exception e)
      {
        serverContext.debugCaught(e);
        serverContext.logMessage(LogSeverity.SEVERE_ERROR,
             "Unable to write a header to LDIF file " +
                  outputFile.getAbsolutePath() + ":  " +
                  StaticUtils.getExceptionMessage(e));
        return TaskReturnState.STOPPED_BY_ERROR;
      }

      return returnState;
    }
    finally
    {
      try
      {
        ldifWriter.close();
      }
      catch (final Exception e)
      {
        serverContext.debugCaught(e);
        serverContext.logMessage(LogSeverity.SEVERE_ERROR,
             "An error occurred while closing file " +
                  outputFile.getAbsolutePath() + " for task " +
                  taskContext.getTaskEntryDN() + ":  " +
                  StaticUtils.getExceptionMessage(e));
      }
    }
  }



  /**
   * Indicates whether this task may be interrupted before it has completed
   * (e.g., canceled by an administrator or aborted at server shutdown).  It is
   * particularly important that potentially long-running tasks be interruptible
   * so that they do not impede server shutdown or consume excessive resources.
   *
   * @return  {@code true} if this task may be interrupted before it has
   *          completed, or {@code false} if it cannot be interrupted.
   */
  @Override()
  public boolean isInterruptible()
  {
    return false;
  }



  /**
   * Attempts to interrupt the execution of this task.  This should only be
   * called if the {@link #isInterruptible} method returns {@code true}.
   *
   * @param  interruptState   The return state that should be used for the task
   *                          if it is successfully interrupted.
   * @param  interruptReason  A message that provides a reason that the task has
   *                          been interrupted.
   */
  @Override()
  public void interruptTask(final TaskReturnState interruptState,
                            final String interruptReason)
  {
    // No action is performed by default, since this task doesn't support
    // being interrupted.
  }



  /**
   * 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<List<String>,String>(1);

    exampleMap.put(
         Arrays.asList(
              ARG_NAME_ATTR + "=description"),
         "Prevent the 'description' attribute from being targeted by " +
              "client operations or returned in search result entries.");

    return exampleMap;
  }



  /**
   * Indicates that the provided search result entry has been returned by the
   * server and may be processed by this search result listener.
   *
   * @param  searchEntry  The search result entry that has been returned by the
   *                      server.
   */
  public void searchEntryReturned(final SearchResultEntry searchEntry)
  {
    try
    {
      ldifWriter.writeEntry(searchEntry);
    }
    catch (final Exception e)
    {
      serverContext.debugCaught(e);
      serverContext.logMessage(LogSeverity.SEVERE_ERROR,
           "An error occurred while attempting to write matching entry " +
                searchEntry.getDN() + " to file " +
                outputFile.getAbsolutePath() + " for task " +
                taskContext.getTaskEntryDN() + ":  " +
                StaticUtils.getExceptionMessage(e));
      returnState = TaskReturnState.COMPLETED_WITH_ERRORS;
    }
  }



  /**
   * Indicates that the provided search result reference has been returned by
   * the server and may be processed by this search result listener.
   *
   * @param  searchReference  The search result reference that has been returned
   *                          by the server.
   */
  public void searchReferenceReturned(
                   final SearchResultReference searchReference)
  {
    try
    {
      ldifWriter.writeComment(
           "Received search result reference with URLs " +
                Arrays.toString(searchReference.getReferralURLs()),
           true, true);
    }
    catch (final Exception e)
    {
      serverContext.debugCaught(e);
      serverContext.logMessage(LogSeverity.SEVERE_ERROR,
           "An error occurred while attempting to information about " +
                "search result reference " +
                Arrays.toString(searchReference.getReferralURLs()) +
                " to file " + outputFile.getAbsolutePath() + " for task " +
                taskContext.getTaskEntryDN() + ":  " +
                StaticUtils.getExceptionMessage(e));
      returnState = TaskReturnState.COMPLETED_WITH_ERRORS;
    }
  }
}