/*
* 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-2016 UnboundID Corp.
*/
package com.unboundid.directory.sdk.examples;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.unboundid.directory.sdk.proxy.api.ProxyTransformation;
import com.unboundid.directory.sdk.proxy.config.ProxyTransformationConfig;
import com.unboundid.directory.sdk.proxy.types.ProxyOperationContext;
import com.unboundid.directory.sdk.proxy.types.ProxyServerContext;
import com.unboundid.ldap.sdk.AddRequest;
import com.unboundid.ldap.sdk.Attribute;
import com.unboundid.ldap.sdk.CompareRequest;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.Modification;
import com.unboundid.ldap.sdk.ModifyRequest;
import com.unboundid.ldap.sdk.ReadOnlySearchRequest;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchRequest;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.StringArgument;
/**
* This class provides a simple example of a proxy transformation which will
* attempt to replace references to one attribute with references to another.
* References will be replaced in add, compare, modify, and search requests, and
* also in search result entries. It takes two configuration arguments:
* <UL>
* <LI>client-attribute -- The client-side name for the target attribute.</LI>
* <LI>server-attribute -- The server-side name for the target attribute.</LI>
* </UL>
*
* References to the client attribute will be replaced with references to the
* server attribute in requests, and references to the server attribute will be
* replaced with references to the client attribute in responses.
*/
public final class ExampleProxyTransformation
extends ProxyTransformation
{
/**
* The name of the argument that will be used to specify the name of the
* client attribute.
*/
private static final String ARG_NAME_CLIENT_ATTR = "client-attribute";
/**
* The name of the argument that will be used to specify the name of the
* server attribute.
*/
private static final String ARG_NAME_SERVER_ATTR = "server-attribute";
// The server context for the server in which this extension is running.
private ProxyServerContext serverContext;
// The name of the client attribute.
private volatile String clientAttribute;
// The name of the server attribute.
private volatile String serverAttribute;
/**
* Creates a new instance of this proxy transformation. All proxy
* transformation implementations must include a default constructor, but any
* initialization should generally be done in the
* {@code initializeProxyTransformation} method.
*/
public ExampleProxyTransformation()
{
// 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 Proxy Transformation";
}
/**
* 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 proxy transformation serves an example that may be used to " +
"demonstrate the process for creating a third-party proxy " +
"transformation. It will alter requests to replace references " +
"to a specified client-side attribute with a given server-side " +
"attribute, and will alter responses to replace references to the " +
"server-side attribute with the client-side attribute. Note that " +
"because this proxy transformation is primarily intended for " +
"example use, it does not perform the transformation in all " +
"areas in which it might be desired, including in entry DNs and " +
"in the values of controls or extended operations."
};
}
/**
* Updates the provided argument parser to define any configuration arguments
* which may be used by this proxy transformation. 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 proxy transformation.
*
* @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 client attribute.
Character shortIdentifier = null;
String longIdentifier = ARG_NAME_CLIENT_ATTR;
boolean required = true;
int maxOccurrences = 1;
String placeholder = "{attr}";
String description = "The name that clients will use to reference " +
"the target attribute.";
parser.addArgument(new StringArgument(shortIdentifier, longIdentifier,
required, maxOccurrences, placeholder, description));
// Add an argument that allows you to specify the server attribute.
shortIdentifier = null;
longIdentifier = ARG_NAME_SERVER_ATTR;
required = true;
maxOccurrences = 1;
placeholder = "{attr}";
description = "The name that the server will use to reference " +
"the target attribute.";
parser.addArgument(new StringArgument(shortIdentifier, longIdentifier,
required, maxOccurrences, placeholder, description));
}
/**
* Initializes this proxy transformation.
*
* @param serverContext A handle to the server context for the server in
* which this extension is running.
* @param config The general configuration for this proxy
* transformation.
* @param parser The argument parser which has been initialized from
* the configuration for this proxy transformation.
*
* @throws LDAPException If a problem occurs while initializing this proxy
* transformation.
*/
@Override()
public void initializeProxyTransformation(
final ProxyServerContext serverContext,
final ProxyTransformationConfig config,
final ArgumentParser parser)
throws LDAPException
{
serverContext.debugInfo("Beginning proxy transformation initialization");
this.serverContext = serverContext;
// Get the client attribute.
final StringArgument clientArg =
(StringArgument) parser.getNamedArgument(ARG_NAME_CLIENT_ATTR);
clientAttribute = clientArg.getValue();
// Get the server attribute.
final StringArgument serverArg =
(StringArgument) parser.getNamedArgument(ARG_NAME_SERVER_ATTR);
serverAttribute = serverArg.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 proxy
* transformation.
* @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 ProxyTransformationConfig 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 proxy
* transformation.
* @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 ProxyTransformationConfig config,
final ArgumentParser parser,
final List<String> adminActionsRequired,
final List<String> messages)
{
// Get the new client attribute.
final StringArgument clientArg =
(StringArgument) parser.getNamedArgument(ARG_NAME_CLIENT_ATTR);
final String newClientAttr = clientArg.getValue();
// Get the new server attribute.
final StringArgument serverArg =
(StringArgument) parser.getNamedArgument(ARG_NAME_SERVER_ATTR);
final String newServerAttr = serverArg.getValue();
clientAttribute = newClientAttr;
serverAttribute = newServerAttr;
return ResultCode.SUCCESS;
}
/**
* Performs any cleanup which may be necessary when this proxy transformation
* is to be taken out of service.
*/
@Override()
public void finalizeProxyTransformation()
{
// No finalization is required.
}
/**
* Applies any necessary transformation to the provided add request.
*
* @param operationContext Information about the operation being processed
* in the Proxy.
* @param addRequest The add request to be transformed.
*
* @return A new add request which has been transformed as necessary, or the
* original request if no transformation is required or the provided
* add request has been updated in place.
*
* @throws LDAPException If a problem is encountered while processing the
* transformation, or if the provided request should
* not be forwarded to a backend server.
*/
@Override()
public AddRequest transformAddRequest(
final ProxyOperationContext operationContext,
final AddRequest addRequest)
throws LDAPException
{
// Get the client-side attribute from the add request. If the add request
// doesn't have the target attribute then we'll just return the original
// request.
final Attribute a = addRequest.getAttribute(clientAttribute);
if (a == null)
{
return addRequest;
}
// Duplicate the add request and replace the client-side attribute with the
// server-side version.
final AddRequest newRequest = addRequest.duplicate();
newRequest.removeAttribute(clientAttribute);
newRequest.addAttribute(serverAttribute, a.getValueByteArrays());
return newRequest;
}
/**
* Applies any necessary transformation to the provided compare request.
*
* @param operationContext Information about the operation being processed
* in the Proxy.
* @param compareRequest The compare request to be transformed.
*
* @return A new compare request which has been transformed as necessary, or
* the original request if no transformation is required or the
* provided compare request has been updated in place.
*
* @throws LDAPException If a problem is encountered while processing the
* transformation, or if the provided request should
* not be forwarded to a backend server.
*/
@Override()
public CompareRequest transformCompareRequest(
final ProxyOperationContext operationContext,
final CompareRequest compareRequest)
throws LDAPException
{
// If the target attribute is the client attribute, then replace it.
// Otherwise, return the original request.
if (compareRequest.getAttributeName().equalsIgnoreCase(clientAttribute))
{
final CompareRequest newRequest = compareRequest.duplicate();
newRequest.setAttributeName(serverAttribute);
return newRequest;
}
else
{
return compareRequest;
}
}
/**
* Applies any necessary transformation to the provided modify request.
*
* @param operationContext Information about the operation being processed
* in the Proxy.
* @param modifyRequest The modify request to be transformed.
*
* @return A new modify request which has been transformed as necessary, or
* the original request if no transformation is required or the
* provided modify request has been updated in place.
*
* @throws LDAPException If a problem is encountered while processing the
* transformation, or if the provided request should
* not be forwarded to a backend server.
*/
@Override()
public ModifyRequest transformModifyRequest(
final ProxyOperationContext operationContext,
final ModifyRequest modifyRequest)
throws LDAPException
{
// See if any of the modifications reference the client attribute.
boolean found = false;
final List<Modification> mods = modifyRequest.getModifications();
for (final Modification m : mods)
{
if (m.getAttributeName().equalsIgnoreCase(clientAttribute))
{
found = true;
break;
}
}
// If the client attribute wasn't found, then return the original request.
if (! found)
{
return modifyRequest;
}
// Duplicate the request and replace the modification set.
final ModifyRequest newRequest = modifyRequest.duplicate();
final ArrayList<Modification> newMods =
new ArrayList<Modification>(mods.size());
for (final Modification m : mods)
{
if (m.getAttributeName().equalsIgnoreCase(clientAttribute))
{
newMods.add(new Modification(m.getModificationType(), serverAttribute,
m.getRawValues()));
}
else
{
newMods.add(m);
}
}
newRequest.setModifications(newMods);
return newRequest;
}
/**
* Applies any necessary transformation to the provided search request.
*
* @param operationContext Information about the operation being processed
* in the Proxy.
* @param searchRequest The search request to be transformed.
*
* @return A new search request which has been transformed as necessary, or
* the original request if no transformation is required or the
* provided search request has been updated in place.
*
* @throws LDAPException If a problem is encountered while processing the
* transformation, or if the provided request should
* not be forwarded to a backend server.
*/
@Override()
public SearchRequest transformSearchRequest(
final ProxyOperationContext operationContext,
final SearchRequest searchRequest)
throws LDAPException
{
// See if the filter or requested attribute list contains the client
// attribute.
boolean foundInAttrs = false;
final String[] attrs = searchRequest.getAttributes();
for (final String s : attrs)
{
if (s.equalsIgnoreCase(clientAttribute))
{
foundInAttrs = true;
break;
}
}
final Filter filter = searchRequest.getFilter();
final boolean foundInFilter = filterReferencesClientAttr(filter);
if (! (foundInAttrs || foundInFilter))
{
return searchRequest;
}
final SearchRequest newRequest = searchRequest.duplicate();
if (foundInAttrs)
{
final String[] newAttrs = new String[attrs.length];
for (int i=0; i < attrs.length; i++)
{
if (attrs[i].equalsIgnoreCase(clientAttribute))
{
newAttrs[i] = serverAttribute;
}
else
{
newAttrs[i] = attrs[i];
}
}
newRequest.setAttributes(newAttrs);
}
if (foundInFilter)
{
newRequest.setFilter(updateFilter(filter));
}
return newRequest;
}
/**
* Indicates whether the provided filter references the client attribute.
*
* @param f The filter for which to make the determination.
*
* @return {@code true} if the provided filter references the client
* attribute, or {@code false} if not.
*/
private boolean filterReferencesClientAttr(final Filter f)
{
switch (f.getFilterType())
{
case Filter.FILTER_TYPE_AND:
case Filter.FILTER_TYPE_OR:
for (final Filter comp : f.getComponents())
{
if (filterReferencesClientAttr(comp))
{
return true;
}
}
return false;
case Filter.FILTER_TYPE_NOT:
return filterReferencesClientAttr(f.getNOTComponent());
default:
final String attrName = f.getAttributeName();
return ((attrName != null) &&
(attrName.equalsIgnoreCase(clientAttribute)));
}
}
/**
* Creates a new filter with all references to the client attribute replaced
* by references to the server attribute.
*
* @param f The filter to be replaced.
*
* @return The updated filter.
*/
private Filter updateFilter(final Filter f)
{
switch (f.getFilterType())
{
case Filter.FILTER_TYPE_AND:
Filter[] comps = f.getComponents();
Filter[] newComps = new Filter[comps.length];
for (int i=0; i < comps.length; i++)
{
newComps[i] = updateFilter(comps[i]);
}
return Filter.createANDFilter(newComps);
case Filter.FILTER_TYPE_OR:
comps = f.getComponents();
newComps = new Filter[comps.length];
for (int i=0; i < comps.length; i++)
{
newComps[i] = updateFilter(comps[i]);
}
return Filter.createORFilter(newComps);
case Filter.FILTER_TYPE_NOT:
return Filter.createNOTFilter(updateFilter(f.getNOTComponent()));
case Filter.FILTER_TYPE_EQUALITY:
String attrName = f.getAttributeName();
if (attrName.equalsIgnoreCase(clientAttribute))
{
return Filter.createEqualityFilter(serverAttribute,
f.getAssertionValueBytes());
}
else
{
return f;
}
case Filter.FILTER_TYPE_SUBSTRING:
attrName = f.getAttributeName();
if (attrName.equalsIgnoreCase(clientAttribute))
{
return Filter.createSubstringFilter(serverAttribute,
f.getSubInitialBytes(), f.getSubAnyBytes(),
f.getSubFinalBytes());
}
else
{
return f;
}
case Filter.FILTER_TYPE_GREATER_OR_EQUAL:
attrName = f.getAttributeName();
if (attrName.equalsIgnoreCase(clientAttribute))
{
return Filter.createGreaterOrEqualFilter(serverAttribute,
f.getAssertionValueBytes());
}
else
{
return f;
}
case Filter.FILTER_TYPE_LESS_OR_EQUAL:
attrName = f.getAttributeName();
if (attrName.equalsIgnoreCase(clientAttribute))
{
return Filter.createLessOrEqualFilter(serverAttribute,
f.getAssertionValueBytes());
}
else
{
return f;
}
case Filter.FILTER_TYPE_PRESENCE:
attrName = f.getAttributeName();
if (attrName.equalsIgnoreCase(clientAttribute))
{
return Filter.createPresenceFilter(serverAttribute);
}
else
{
return f;
}
case Filter.FILTER_TYPE_APPROXIMATE_MATCH:
attrName = f.getAttributeName();
if (attrName.equalsIgnoreCase(clientAttribute))
{
return Filter.createApproximateMatchFilter(serverAttribute,
f.getAssertionValueBytes());
}
else
{
return f;
}
case Filter.FILTER_TYPE_EXTENSIBLE_MATCH:
attrName = f.getAttributeName();
if ((attrName != null) && attrName.equalsIgnoreCase(clientAttribute))
{
return Filter.createExtensibleMatchFilter(serverAttribute,
f.getMatchingRuleID(), f.getDNAttributes(),
f.getAssertionValueBytes());
}
else
{
return f;
}
default:
// This should never happen.
return f;
}
}
/**
* Applies any necessary transformation to the provided search result entry.
*
* @param operationContext Information about the operation being processed
* in the Proxy.
* @param searchRequest The search request that is being processed.
* @param searchEntry The search result entry to be transformed.
*
* @return A new search result entry which has been transformed as necessary,
* the original search result entry if no transformation is required,
* or {@code null} if the entry should not be returned to the client.
*/
@Override()
public SearchResultEntry transformSearchResultEntry(
final ProxyOperationContext operationContext,
final ReadOnlySearchRequest searchRequest,
final SearchResultEntry searchEntry)
{
// Get the server-side attribute from the entry. If the entry doesn't have
// the target attribute then we'll just return the original version.
final Attribute a = searchEntry.getAttribute(serverAttribute);
if (a == null)
{
return searchEntry;
}
// Duplicate the entry and replace the server-side attribute with the
// client-side version.
final Entry newEntry = searchEntry.duplicate();
newEntry.removeAttribute(serverAttribute);
newEntry.addAttribute(clientAttribute, a.getValueByteArrays());
return new SearchResultEntry(searchEntry.getMessageID(), newEntry,
searchEntry.getControls());
}
/**
* 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_CLIENT_ATTR + "=userID",
ARG_NAME_SERVER_ATTR + "=uid"),
"Replace client-side references to a 'userID' attribute with " +
"server-side references to a 'uid' attribute.");
return exampleMap;
}
/**
* Appends a string representation of this proxy transformation to the
* provided buffer.
*
* @param buffer The buffer to which the string representation should be
* appended.
*/
@Override()
public void toString(final StringBuilder buffer)
{
buffer.append("ExampleProxyTransformation(clientAttribute='");
buffer.append(clientAttribute);
buffer.append("', serverAttribute='");
buffer.append(serverAttribute);
buffer.append("')");
}
}
|