UnboundID Server SDK

Ping Identity
UnboundID Server SDK Documentation

ExampleVelocityContextProvider.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 2013-2024 Ping Identity Corporation
 */
package com.unboundid.directory.sdk.examples;


import com.unboundid.directory.sdk.common.api.VelocityContextProvider;
import com.unboundid.directory.sdk.common.config.VelocityContextProviderConfig;
import com.unboundid.directory.sdk.common.types.InternalConnection;
import com.unboundid.directory.sdk.common.types.ServerContext;
import com.unboundid.directory.sdk.common.types.VelocityContext;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.DNArgument;
import com.unboundid.util.args.StringArgument;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;


/**
 * This class provides a simple example of a Velocity context provider that
 * exposes a directory entry for use in a Velocity template. This provider takes
 * the following configuration arguments:
 * <UL>
 *   <LI>request-attr -- The name of the HTTP request attribute in which this
 *       provider expects to find a string used as the naming attribute value of
 *       the entry to retrieve from the directory.</LI>
 *   <LI>base-dn -- The base DN where this provider expects to find directory
 *       entries. The value of this attribute is used to form the DN of an
 *       entry to retrieve from the directory.</LI>
 *   <LI>naming-attr -- The attribute used as the RDN of requested entries.
 *       The value of this attribute is used to form the DN of an entry to
 *       retrieve from the directory.</LI>
 * </UL>
 * <p>
 * For example if <i>request-attr</i> is 'name' and a client sends a request
 * to the server with a query string parameter like:
 * <PRE>
 * http://example.com:8080/view/entry?name=user.0
 * </PRE>
 * then if <i>base-dn</i> is 'ou=people,dc=example,dc=com' and
 * <i>naming-attr</i> is 'uid' and an entry exists at
 * uid=user.0,ou=people,dc=example,dc=com, the corresponding entry will be put
 * into the Velocity context making it available to the template resolved by
 * the Velocity servlet for the request.  The template may have references
 * to $entry that will be replaced at rendering time with the LDAP SDK entry
 * instance.  So for instance the template might print the DN of the entry
 * using the reference $entry.DN.
 */
public final class ExampleVelocityContextProvider extends
        VelocityContextProvider
{

  private static final String ARG_REQ_ATTR = "request-attr";

  private static final String ARG_BASE_DN = "base-dn";

  private static final String ARG_NAMING_ATTR = "naming-attr";

  private ServerContext serverContext;

  private volatile DN baseDN;

  private volatile String namingAttr;

  private volatile String requestAttr;


  /**
   * Constructs a default instance.
   */
  public ExampleVelocityContextProvider()
  {
    // 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 Velocity Context Provider";
  }



  /**
   * 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 Velocity context provider serves as an example that may be used " +
          "to demonstrate the process for creating a third-party Velocity " +
          "context provider.  It will obtain an entry from the local server " +
          "using an expected request parameter and place the entry into the " +
          "Velocity context making it available to templates."
    };
  }



  /**
   * Updates the provided argument parser to define any configuration arguments
   * which may be used by this Velocity context provider.  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 Velocity context
   *               provider.
   * @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 an entry's base DN.
    Character shortIdentifier = null;
    String longIdentifier = ARG_BASE_DN;
    boolean required = true;
    int maxOccurrences = 1;
    String placeholder = "{dn}";
    String description = "The base DN of entries.";

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


    // Add an argument that allows you to specify an entry's naming attribute.
    shortIdentifier = null;
    longIdentifier = ARG_NAMING_ATTR;
    required = true;
    maxOccurrences = 1;
    placeholder = "{attr}";
    description = "The name of the attribute used as the RDN " +
            "for entries.";

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


    // Add an argument that identifies the HTTP request attribute to be used to
    // obtain an entry's naming attribute value.  The value of this argument
    // identifies an entry in the directory by its naming attribute below the
    // base DN.
    shortIdentifier = null;
    longIdentifier = ARG_REQ_ATTR;
    required = true;
    maxOccurrences = 1;
    placeholder = "{attr}";
    description = "The name of the HTTP request attribute from which " +
            "to obtain an entry's naming attribute value.";

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



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

    // Get the base DN.
    final DNArgument baseDNArg =
            (DNArgument) parser.getNamedArgument(ARG_BASE_DN);
    baseDN = baseDNArg.getValue();

    // Get the naming attribute.
    final StringArgument nameAttrArg =
            (StringArgument) parser.getNamedArgument(ARG_NAMING_ATTR);
    namingAttr = nameAttrArg.getValue();

    // Get the request attribute.
    final StringArgument reqAttrArg =
            (StringArgument) parser.getNamedArgument(ARG_REQ_ATTR);
    requestAttr = reqAttrArg.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 Velocity
   *                            context provider.
   * @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 VelocityContextProviderConfig config,
          final ArgumentParser parser,
          final List<String> unacceptableReasons)
  {
    // The argument parser will handle all of the necessary validation, so
    // we don't need to do anything here.
    return true;
  }



  /**
   * Attempts to apply the configuration contained in the provided argument
   * parser.
   *
   * @param config               The general configuration for this Velocity
   *                             context provider.
   * @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 VelocityContextProviderConfig config,
          final ArgumentParser parser,
          final List<String> adminActionsRequired,
          final List<String> messages)
  {

    // Get the new base DN.
    final DNArgument baseDNArg =
            (DNArgument) parser.getNamedArgument(ARG_BASE_DN);
    baseDN = baseDNArg.getValue();

    // Get the new naming attribute.
    final StringArgument nameAttrArg =
            (StringArgument) parser.getNamedArgument(ARG_NAMING_ATTR);
    namingAttr = nameAttrArg.getValue();

    // Get the new request attribute.
    final StringArgument reqAttrArg =
            (StringArgument) parser.getNamedArgument(ARG_REQ_ATTR);
    requestAttr = reqAttrArg.getValue();

    return ResultCode.SUCCESS;
  }



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



  /**
   * 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_BASE_DN + "=ou=people,dc=example,dc=com"),
            "Specify the base DN where entries exist.");

    exampleMap.put(
            Arrays.asList(ARG_NAMING_ATTR + "=uid"),
            "Specify the naming attribute used to identify entries.");

    exampleMap.put(
            Arrays.asList(ARG_REQ_ATTR + "=entry"),
            "Specify the HTTP request attribute used to obtain the entry's" +
                    " naming attribute value.");

    return exampleMap;
  }



  /**
   * Given an HTTP GET request expected to include a request attribute
   * with a value that matches the naming attribute of an entry in the
   * directory, obtain the entry and add it to the context, making it
   * available to templates.
   *
   * @param context  to update.
   * @param request  for the view implemented by a template.
   * @param response to be sent to the request.
   */
  @Override
  public void handleGet(final VelocityContext context,
                        final HttpServletRequest request,
                        final HttpServletResponse response)
  {
    // Determine the name of the entry being requested from the
    // configured request attribute.
    String[] values = request.getParameterMap().get(requestAttr);
    String name = values != null && values.length > 0 ? values[0] : null;

    try
    {

      if (name == null)
      {
        response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                "Expected \'" + requestAttr + "\' parameter.");
      }

      // First check to see whether the entry has been stored as a result
      // of a previous request.
      Object o = getNamedObject(name, request);
      if (o instanceof Entry)
      {
        context.put("entry", o);
      }
      else
      {
        // The entry has not been previously requested for the particular
        // configured scope.
        String dn = namingAttr + "=" + name + "," + baseDN.toString();
        InternalConnection conn = serverContext.getInternalRootConnection();

        try
        {
          Entry entry = conn.getEntry(dn);
          if (entry == null)
          {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "No entry found at " + dn + ".");
          }
          else
          {
            context.put("entry", entry);
            setNamedObject(name, entry, request);
          }
        }
        catch (LDAPException le)
        {
          response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                  "Error searching for entry " + dn + ".");
        }
      }
    }
    catch (IOException ioe)
    {
      serverContext.debugCaught(ioe);
    }
  }

}