messages)
{
setConfig(config, parser);
return ResultCode.SUCCESS;
}
/**
* Sets the configuration for this plugin. This is a centralized place
* where the configuration is initialized or updated.
*
* @param config The general configuration for this LDAP sync
* destination plugin.
* @param parser The argument parser which has been initialized from
* the configuration for this LDAP sync destination
* plugin.
*/
private void setConfig(
final LDAPSyncDestinationPluginConfig config,
final ArgumentParser parser)
{
configWriteLock.lock();
try
{
this.referenceAttribute =((StringArgument)parser.getNamedArgument(
ARG_NAME_REFERENCE_ATTRIBUTE)).getValue();
this.referencedEntryKeyAttribute =
((StringArgument)parser.getNamedArgument(
ARG_NAME_REFERENCED_ENTRY_KEY_ATTRIBUTE)).getValue();
this.searchBaseDn = ((DNArgument)parser.getNamedArgument(
ARG_NAME_SEARCH_BASE_DN)).getValue();
}
finally
{
configWriteLock.unlock();
}
}
/**
* This method is called after an attempt to fetch a destination entry. An
* connection to the destination server is provided along with the
* {@code SearchRequest} that was sent to the server. This method is
* overridden by plugins that need to manipulate the search results that
* are returned to the Sync Pipe. This can include filtering out certain
* entries, remove information from the entries, or adding additional
* information, possibly by doing a followup LDAP search.
*
* This method might be called multiple times for a single synchronization
* operation, specifically when there are multiple search criteria or
* multiple base DNs defined for the Sync Destination.
*
* This method will not be called if the search fails, for instance, if
* the base DN of the search does not exist.
*
* @param destinationConnection A connection to the destination server.
* @param searchRequest The search request that the LDAP Sync
* Destination used to fetch the entry.
* @param fetchedEntries A list of entries that have been fetched.
* When the search criteria matches multiple
* entries, they will all be returned. Entries
* in this list can be edited directly, and the
* list can be edited as well.
* @param operation The synchronization operation for this
* change.
*
* @return The result of the plugin processing.
*
* @throws LDAPException In general subclasses should not catch
* LDAPExceptions that are thrown when
* using the LDAPInterface unless there
* are specific exceptions that are
* expected. The Data Sync Server
* will handle LDAPExceptions in an
* appropriate way based on the specific
* cause of the exception. For example,
* some errors will result in the
* SyncOperation being retried, and others
* will trigger fail over to a different
* server. Plugins should only throw
* LDAPException for errors related to
* communication with the LDAP server.
* Use the return code to indicate other
* types of errors, which might require
* retry.
*/
@Override
public PostStepResult postFetch(final LDAPInterface destinationConnection,
final SearchRequest searchRequest,
final List fetchedEntries,
final SyncOperation operation)
throws LDAPException
{
configReadLock.lock();
try
{
for (Entry entry: fetchedEntries)
{
String[] referenceDns = entry.getAttributeValues(referenceAttribute);
if (referenceDns == null)
{
serverContext.debugInfo("Entry " + entry.getDN() + " does not have " +
"any values for the " + referencedEntryKeyAttribute +
"attribute.");
continue;
}
List keyValues = new ArrayList(referenceDns.length);
for (int i = 0; i < referenceDns.length; i++)
{
String referenceDn = referenceDns[i];
Entry referencedEntry = destinationConnection.getEntry(referenceDn,
referencedEntryKeyAttribute);
if (referencedEntry != null)
{
String keyValue =
referencedEntry.getAttributeValue(referencedEntryKeyAttribute);
if (keyValue != null)
{
keyValues.add(keyValue);
}
else
{
operation.logError("For fetched entry '" + entry.getDN() + "', " +
"the example plugin could not find a corresponding " +
referencedEntryKeyAttribute + " value for referenced entry " +
referenceDn + " but the referenced entry does exist.");
}
}
else
{
operation.logError("For fetched entry '" + entry.getDN() + "', " +
"the example plugin could not find the entry, " +
referenceDn + ", which is referenced by " + referenceAttribute +
".");
}
}
if (keyValues.isEmpty())
{
operation.logInfo("Example plugin removed the " + referenceAttribute +
" attribute because no entries could be found to match the list " +
"DNs: " + Arrays.asList(referenceDns));
entry.removeAttribute(referenceAttribute);
}
else
{
operation.logInfo("Example plugin replacing DN(s)=" +
Arrays.asList(referenceDns) + " in attribute " +
referenceAttribute + " with " + referencedEntryKeyAttribute +
" value(s)=" + keyValues);
entry.setAttribute(new Attribute(referenceAttribute, keyValues));
}
}
return PostStepResult.CONTINUE;
}
finally
{
configReadLock.unlock();
}
}
/**
* This method is called before a destination entry is created. A
* connection to the destination server is provided along with the
* {@code Entry} that will be sent to the server. This method is
* overridden by plugins that need to alter the entry before it is created
* at the server.
*
* @param destinationConnection A connection to the destination server.
* @param entryToCreate The entry that will be created at the
* destination. A plugin that wishes to
* create the entry should be sure to return
* {@code PreStepResult#SKIP_CURRENT_STEP}.
* @param operation The synchronization operation for this
* change.
*
* @return The result of the plugin processing.
*
* @throws LDAPException In general subclasses should not catch
* LDAPExceptions that are thrown when
* using the LDAPInterface unless there
* are specific exceptions that are
* expected. The Data Sync Server
* will handle LDAPExceptions in an
* appropriate way based on the specific
* cause of the exception. For example,
* some errors will result in the
* SyncOperation being retried, and others
* will trigger fail over to a different
* server. Plugins should only throw
* LDAPException for errors related to
* communication with the LDAP server.
* Use the return code to indicate other
* types of errors, which might require
* retry.
*/
@Override
public PreStepResult preCreate(final LDAPInterface destinationConnection,
final Entry entryToCreate,
final SyncOperation operation)
throws LDAPException
{
try
{
configReadLock.lock();
String[] referenceValues =
entryToCreate.getAttributeValues(referenceAttribute);
if (referenceValues == null)
{
return PreStepResult.CONTINUE;
}
List dnValues = new ArrayList(referenceValues.length);
for (int i = 0; i < referenceValues.length; i++)
{
String referenceValue = referenceValues[i];
String dnValue = lookupDnByKeyValue(destinationConnection,
entryToCreate, operation, referenceValue);
if (dnValue != null)
{
dnValues.add(dnValue);
}
}
if (dnValues.isEmpty())
{
operation.logInfo("Example plugin removed the " + referenceAttribute +
" attribute because no entries could be found to match the list " +
"of values: " + Arrays.asList(referenceValues));
entryToCreate.removeAttribute(referenceAttribute);
}
else
{
operation.logInfo("Example plugin replacing " +
referencedEntryKeyAttribute + " reference values in the " +
referenceAttribute + " attribute: " +
Arrays.asList(referenceValues) +
" with DN value(s)=" + dnValues);
entryToCreate.setAttribute(new Attribute(referenceAttribute, dnValues));
}
return PreStepResult.CONTINUE;
}
finally
{
configReadLock.unlock();
}
}
/**
* This method is called before a destination entry is modified. A
* connection to the destination server is provided along with the
* {@code Entry} that will be sent to the server. This method is
* overridden by plugins that need to perform some processing on an entry
* before it is modified.
*
* @param destinationConnection A connection to the destination server.
* @param entryToModify The entry that will be modified at the
* destination. A plugin that wishes to
* modify the entry should be sure to return
* {@code PreStepResult#SKIP_CURRENT_STEP}.
* @param modsToApply A modifiable list of the modifications to
* apply at the server.
* @param operation The synchronization operation for this
* change.
*
* @return The result of the plugin processing.
*
* @throws LDAPException In general subclasses should not catch
* LDAPExceptions that are thrown when
* using the LDAPInterface unless there
* are specific exceptions that are
* expected. The Data Sync Server
* will handle LDAPExceptions in an
* appropriate way based on the specific
* cause of the exception. For example,
* some errors will result in the
* SyncOperation being retried, and others
* will trigger fail over to a different
* server. Plugins should only throw
* LDAPException for errors related to
* communication with the LDAP server.
* Use the return code to indicate other
* types of errors, which might require
* retry.
*/
@Override
public PreStepResult preModify(final LDAPInterface destinationConnection,
final Entry entryToModify,
final List modsToApply,
final SyncOperation operation)
throws LDAPException
{
try
{
configReadLock.lock();
List updatedModsToApply =
new ArrayList(modsToApply.size());
for (Modification modification : modsToApply)
{
// Let any modification to non-referenceAttribute attributes pass
// straight through.
if (!referenceAttribute.equalsIgnoreCase(
modification.getAttributeName()))
{
updatedModsToApply.add(modification);
continue;
}
// For the referenceAttribute attribute, convert keys to DNs.
String[] keyValues = modification.getValues();
List dnValues = new ArrayList(keyValues.length);
for (String keyValue : keyValues)
{
String dnValue = lookupDnByKeyValue(destinationConnection,
entryToModify,
operation,
keyValue);
if (dnValue != null)
{
dnValues.add(dnValue);
}
}
// If we started off with no values, or ended up with at least one
// DN, then let this mod pass through.
if ((keyValues.length == 0) || !dnValues.isEmpty())
{
String[] dnValuesArr = dnValues.toArray(new String[0]);
Modification updatedMod =
new Modification(modification.getModificationType(),
modification.getAttributeName(), dnValuesArr);
updatedModsToApply.add(updatedMod);
}
}
// We can't edit the mods in place, so we just replace them with the
// list that we built up.
modsToApply.clear();
modsToApply.addAll(updatedModsToApply);
if (modsToApply.isEmpty())
{
// If there are no mods, then skip the current step.
return PreStepResult.SKIP_CURRENT_STEP;
}
else
{
return PreStepResult.CONTINUE;
}
}
finally
{
configReadLock.unlock();
}
}
/**
* This method is called prior to executing any add, modify, delete, or
* search from the destination but after the respective pre method (e.g
* preFetch or preModify). A connection to the destination server is provided
* along with the {@code UpdatableLDAPRequest} that will be sent to the
* server. this method is overridden by plugins that need to modify the
* LDAP request prior to execution. For example, attaching a {@code Control}
* to the request. Callers of this method can use {@code instanceof}
* to determine which type of LDAP request is being made.
*
* @param destinationConnection A connection to the destination server.
* @param request The LDAP request that will be sent to
* the destination server.
* @param operation The synchronization operation for this
* change.
*
* @return The result of the plugin processing. Be very careful when
* returning {@code PreStepResult#RETRY_OPERATION_UNLIMITED} as this
* can stall all in flight operations until this operation completes.
* This return value should only be used in situations where a
* remote service (e.g., the LDAP server) is unavailable. In this
* case, it's preferable to just throw the underlying LDAPException,
* which the Data Sync Server will handle correctly based on
* the type of the operation.
*
* @throws LDAPException In general subclasses should not catch
* LDAPExceptions that are thrown when
* using the LDAPInterface unless there
* are specific exceptions that are
* expected. The Data Sync Server
* will handle LDAPExceptions in an
* appropriate way based on the specific
* cause of the exception. For example,
* some errors will result in the
* SyncOperation being retried, and others
* will trigger fail over to a different
* server. Plugins should only throw
* LDAPException for errors related to
* communication with the LDAP server.
* Use the return code to indicate other
* types of errors, which might require
* retry.
*/
@Override
public PreStepResult transformRequest(final LDAPInterface destinationConnection,
final UpdatableLDAPRequest request,
final SyncOperation operation)
{
// If the request is a delete request then attach
// the soft delete request control.
if (request instanceof DeleteRequest)
{
request.addControl(new SoftDeleteRequestControl());
}
return PreStepResult.CONTINUE;
}
/**
* Returns the DN of the entry with a {@code referencedEntryKeyAttribute}
* value of {@code referenceValue}.
*
* @param destinationConnection A connection to the destination server.
* @param entry The referencing entry.
* @param operation The synchronization operation.
* @param referenceValue The value of the reference attribute.
*
* @return The DN of the referenced entry or {@code null} if there is not
* exactly one referenced entry.
*
* @throws LDAPException If there is a problem retrieving the remote entry.
*/
private String lookupDnByKeyValue(final LDAPInterface destinationConnection,
final Entry entry,
final SyncOperation operation,
final String referenceValue)
throws LDAPException
{
Filter filter = Filter.createEqualityFilter(referencedEntryKeyAttribute,
referenceValue);
SearchResult searchResult = destinationConnection.search(
searchBaseDn.toString(),
SearchScope.SUB,
filter,
"1.1"); // Don't return any attributes.
List entries = searchResult.getSearchEntries();
if (entries.isEmpty())
{
operation.logError("For entry '" +
entry.getDN() + "', " +
"the example plugin could not find any entry that matched " +
filter.toString() + ". This value of the " + referenceAttribute +
" attribute will not be included in the entry.");
return null;
}
else if (entries.size() > 1)
{
operation.logError("For entry '" +
entry.getDN() + "', " +
"the example plugin found " + entries.size() + " entries that " +
"matched " + filter.toString() + " instead of a single one. " +
"This value of the " + referenceAttribute +
" attribute will not be included in the entry.");
return null;
}
else
{
String dnValue = entries.get(0).getDN();
if (serverContext.debugEnabled())
{
serverContext.debugVerbose("When creating entry " +
entry.getDN() + " mapped " + referenceValue + " to " +
dnValue + " for the " + referenceAttribute + " attribute.");
}
return dnValue;
}
}
}