/* * 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-2021 Ping Identity Corporation */ package com.unboundid.directory.sdk.examples; import java.util.List; import java.util.ArrayList; import java.util.Collections; import com.unboundid.directory.sdk.sync.scripting.ScriptedSyncDestination; import com.unboundid.directory.sdk.sync.config.SyncDestinationConfig; import com.unboundid.directory.sdk.sync.types.EndpointException; import com.unboundid.directory.sdk.sync.types.SyncOperation; import com.unboundid.directory.sdk.sync.types.SyncServerContext; import com.unboundid.util.args.ArgumentException; import com.unboundid.util.args.ArgumentParser; import com.unboundid.util.args.DNArgument; import com.unboundid.util.args.IntegerArgument; import com.unboundid.util.args.StringArgument; import com.unboundid.ldap.sdk.ChangeLogEntry; import com.unboundid.ldap.sdk.DN; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPThreadLocalConnectionPool; import com.unboundid.ldap.sdk.Modification; import com.unboundid.ldap.sdk.ResultCode; /** * This example provides an implementation of SyncDestination which can be used * as a one-way notification destination. Its configuration arguments require * the host and port of a target LDAP directory, to which changes are pushed * straight through. For reference, the fetchEntry() method is implemented * as well, although it is not used when a Sync Pipe is in notification * mode. */ public class ExampleScriptedSyncDestination extends ScriptedSyncDestination { // The general configuration for this Sync Destination private volatile SyncDestinationConfig config; // The server context for the server in which this extension is running private SyncServerContext serverContext; // A pool of connections to the destination LDAP server private LDAPThreadLocalConnectionPool connectionPool; /** * {@inheritDoc} */ @Override public void defineConfigArguments(final ArgumentParser parser) throws ArgumentException { StringArgument hostArg = new StringArgument( null, "host", true, 1, "{hostname}", "The hostname of the target directory " + "server to which notifications should be " + "sent."); IntegerArgument portArg = new IntegerArgument( null, "port", true, 1, "{port}", "The port number of the target directory " + "server to which notifications should be " + "sent."); DNArgument bindDNArg = new DNArgument( null, "bind-dn", true, 1, "{bind-DN}", "The bind DN to use for requests to the " + "target directory server."); StringArgument bindPasswordArg = new StringArgument( null, "bind-password", true, 1, "{bind-password}", "The bind password to " + "use for requests to the target directory " + "server."); parser.addArgument(hostArg); parser.addArgument(portArg); parser.addArgument(bindDNArg); parser.addArgument(bindPasswordArg); } /** * Initializes this sync destination. This hook is called when a Sync Pipe * first starts up, or when the resync process first starts up. Any * initialization should be performed here. This method should generally store * the {@link SyncServerContext} in a class * member so that it can be used elsewhere in the implementation. * * @param serverContext A handle to the server context for the server in * which this extension is running. Extensions should * typically store this in a class member. * @param config The general configuration for this object. * @param parser The argument parser which has been initialized from * the configuration for this sync destination. * @throws EndpointException * if a problem occurs while initializing this * sync destination. */ @Override() public void initializeSyncDestination( final SyncServerContext serverContext, final SyncDestinationConfig config, final ArgumentParser parser) throws EndpointException { this.serverContext = serverContext; this.config = config; StringArgument hostArg = parser.getNamedArgument("host"); IntegerArgument portArg = parser.getNamedArgument("port"); DNArgument bindDNArg = parser.getNamedArgument("bind-dn"); StringArgument passwordArg = parser.getNamedArgument("bind-password"); String host = hostArg.getValue(); int port = portArg.getValue(); DN bindDN = bindDNArg.getValue(); String bindPassword = passwordArg.getValue(); try { LDAPConnection conn = new LDAPConnection(host, port, bindDN.toString(), bindPassword); connectionPool = new LDAPThreadLocalConnectionPool(conn); } catch(LDAPException e) { throw new EndpointException(e); } } /** * This hook is called when a Sync Pipe shuts down, or when the resync * process shuts down. Any clean-up of this sync destination should be * performed here. */ @Override public void finalizeSyncDestination() { if(connectionPool != null) { connectionPool.close(); } } /** * Return the URL or path identifying the destination endpoint * to which this extension is transmitting data. This is used for logging * purposes only, so it could just be a server name or hostname and port, etc. * * @return the path to the destination endpoint */ @Override public String getCurrentEndpointURL() { if(connectionPool == null) { return "not connected"; } LDAPConnection conn = null; try { conn = connectionPool.getConnection(); return conn.getConnectedAddress() + ":" + conn.getConnectedPort(); } catch (LDAPException e) { return "not connected"; } finally { connectionPool.releaseConnection(conn); } } /** * Return a full destination entry (in LDAP form) from the destination * endpoint, corresponding to the source {@link Entry} that is passed in. * This method should perform any queries necessary to gather the latest * values for all the attributes to be synchronized and return them in an * Entry. *
* This method only needs to be implemented if the 'synchronization-mode' on * the Sync Pipe is set to 'standard'. If it is set to 'notification', this * method will never be called, and the pipe will pass changes straight * through to one of {@link #createEntry}, {@link #modifyEntry}, or * {@link #deleteEntry}. *
* Note that the if the source entry was renamed (see
* {@link SyncOperation#isModifyDN}), the
* destEntryMappedFromSrc
will have the new DN; the old DN can
* be obtained by calling
* {@link SyncOperation#getDestinationEntryBeforeChange()} and getting the DN
* from there. This method should return the entry in its existing form
* (i.e. with the old DN, before it is changed).
*
* This method must be thread safe, as it will be called repeatedly and
* concurrently by each of the Sync Pipe worker threads as they process
* entries.
* @param destEntryMappedFromSrc
* the LDAP entry which corresponds to the destination "entry" to
* fetch
* @param operation
* the sync operation for this change
* @return a list containing the full LDAP entries that matched this search
* (there may be more than one), or an empty list if no such entry
* exists
* @throws EndpointException
* if there is an error fetching the entry
*/
@Override
public List
* This method must be thread safe, as it will be called repeatedly and
* concurrently by the Sync Pipe worker threads as they process CREATE
* operations.
* @param entryToCreate
* the LDAP entry which corresponds to the destination
* "entry" to create
* @param operation
* the sync operation for this change
* @throws EndpointException
* if there is an error creating the entry
*/
@Override
public void createEntry(final Entry entryToCreate,
final SyncOperation operation)
throws EndpointException
{
try
{
connectionPool.add(entryToCreate);
operation.logInfo("Created entry: " + entryToCreate.getDN());
}
catch(LDAPException e)
{
throw new EndpointException(e);
}
}
/**
* Modify an "entry" on the destination, corresponding to the LDAP
* {@link Entry} that is passed in. This method is responsible for
* transforming the contents of the entry into the desired format and
* transmitting it to the target destination. It may perform multiple updates
* (including inserting or deleting other attributes) in order to fully
* synchronize the entire entry on the destination endpoint.
*
* Note that the if the source entry was renamed (see
* {@link SyncOperation#isModifyDN}), the
*
* This method must be thread safe, as it will be called repeatedly and
* concurrently by the Sync Pipe worker threads as they process MODIFY
* operations.
* @param entryToModify
* the LDAP entry which corresponds to the destination
* "entry" to modify. If the synchronization mode is 'standard',
* this will be the entry that was returned by {@link #fetchEntry};
* otherwise if the synchronization mode is 'notification', this
* will be the destination entry mapped from the source entry, before
* changes are applied.
* @param modsToApply
* a list of Modification objects which should be applied; these will
* have any configured attribute mappings already applied
* @param operation
* the sync operation for this change
* @throws EndpointException
* if there is an error modifying the entry
*/
@Override
public void modifyEntry(final Entry entryToModify,
final List
* This method must be thread safe, as it will be called repeatedly and
* concurrently by the Sync Pipe worker threads as they process DELETE
* operations.
* @param entryToDelete
* the LDAP entry which corresponds to the destination
* "entry" to delete. If the synchronization mode is 'standard',
* this will be the entry that was returned by {@link #fetchEntry};
* otherwise if the synchronization mode is 'notification', this
* will be the mapped destination entry.
* @param operation
* the sync operation for this change
* @throws EndpointException
* if there is an error deleting the entry
*/
@Override
public void deleteEntry(final Entry entryToDelete,
final SyncOperation operation)
throws EndpointException
{
try
{
connectionPool.delete(entryToDelete.getDN());
operation.logInfo("Deleted entry: " + entryToDelete.getDN());
}
catch(LDAPException e)
{
throw new EndpointException(e);
}
}
}
fetchedDestEntry
will have the old DN; the new DN can
* be obtained by calling
* {@link SyncOperation#getDestinationEntryAfterChange()} and getting the DN
* from there.
*