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