001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * docs/licenses/cddl.txt
011 * or http://www.opensource.org/licenses/cddl1.php.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * docs/licenses/cddl.txt.  If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 *      Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 *      Copyright 2010-2021 Ping Identity Corporation
026 */
027package com.unboundid.directory.sdk.sync.scripting;
028
029
030
031import java.util.List;
032
033import com.unboundid.directory.sdk.common.internal.Reconfigurable;
034import com.unboundid.directory.sdk.sync.config.LDAPSyncDestinationPluginConfig;
035import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension;
036import com.unboundid.directory.sdk.sync.types.PostStepResult;
037import com.unboundid.directory.sdk.sync.types.PreStepResult;
038import com.unboundid.directory.sdk.sync.types.SyncOperation;
039import com.unboundid.directory.sdk.sync.types.SyncServerContext;
040import com.unboundid.ldap.sdk.Entry;
041import com.unboundid.ldap.sdk.LDAPException;
042import com.unboundid.ldap.sdk.LDAPInterface;
043import com.unboundid.ldap.sdk.Modification;
044import com.unboundid.ldap.sdk.ResultCode;
045import com.unboundid.ldap.sdk.SearchRequest;
046import com.unboundid.ldap.sdk.UpdatableLDAPRequest;
047import com.unboundid.util.Extensible;
048import com.unboundid.util.ThreadSafety;
049import com.unboundid.util.ThreadSafetyLevel;
050import com.unboundid.util.args.ArgumentException;
051import com.unboundid.util.args.ArgumentParser;
052
053
054
055/**
056 * This class defines an API that must be implemented by scripted extensions
057 * that perform processing on synchronization operations within an LDAP Sync
058 * Destination.  These extensions may be used to
059 * <ul>
060 *   <li>Filter out certain changes from being synchronized.</li>
061 *   <li>Change how an entry is fetched.</li>
062 *   <li>Change how an entry is modified or created.</li>
063 * </ul>
064 * <BR>
065 * A note on exception handling: in general subclasses should not
066 * catch LDAPExceptions that are thrown when using the provided
067 * LDAPInterface unless there are specific exceptions that are
068 * expected.  The Data Sync Server will handle
069 * LDAPExceptions in an appropriate way based on the specific
070 * cause of the exception.  For example, some errors will result
071 * in the SyncOperation being retried, and others will trigger
072 * fail over to a different server.
073 * <BR>
074 * <H2>Configuring Groovy-Scripted LDAP Sync Destination Plugins</H2>
075 * In order to configure a scripted LDAP sync destination plugin based on this
076 * API and written in the Groovy scripting language, use a command like:
077 * <PRE>
078 *      dsconfig create-sync-destination-plugin \
079 *           --plugin-name "<I>{plugin-name}</I>" \
080 *           --type groovy-scripted-ldap \
081 *           --set "script-class:<I>{class-name}</I>" \
082 *           --set "script-argument:<I>{name=value}</I>"
083 * </PRE>
084 * where "<I>{plugin-name}</I>" is the name to use for the LDAP sync destination
085 * plugin instance, "<I>{class-name}</I>" is the fully-qualified name of the
086 * Groovy class written using this API, and "<I>{name=value}</I>" represents
087 * name-value pairs for any arguments to provide to the LDAP sync destination
088 * plugin.  If multiple arguments should be provided to the LDAP sync
089 * destination plugin, then the
090 * "<CODE>--set script-argument:<I>{name=value}</I></CODE>" option should be
091 * provided multiple times.
092 *
093 * @see  com.unboundid.directory.sdk.sync.api.LDAPSyncDestinationPlugin
094 */
095@Extensible()
096@SynchronizationServerExtension(appliesToLocalContent=false,
097     appliesToSynchronizedContent=true)
098@ThreadSafety(level= ThreadSafetyLevel.INTERFACE_THREADSAFE)
099public abstract class ScriptedLDAPSyncDestinationPlugin
100       implements Reconfigurable<LDAPSyncDestinationPluginConfig>
101{
102  /**
103   * Creates a new instance of this LDAP sync destination plugin.  All sync
104   * destination implementations must include a default constructor, but any
105   * initialization should generally be done in the
106   * {@code initializeLDAPSyncDestinationPlugin} method.
107   */
108  public ScriptedLDAPSyncDestinationPlugin()
109  {
110    // No implementation is required.
111  }
112
113
114
115  /**
116   * {@inheritDoc}
117   */
118  public void defineConfigArguments(final ArgumentParser parser)
119         throws ArgumentException
120  {
121    // No arguments will be allowed by default.
122  }
123
124
125
126  /**
127   * Initializes this LDAP sync destination plugin.
128   *
129   * @param  serverContext  A handle to the server context for the server in
130   *                        which this extension is running.
131   * @param  config         The general configuration for this LDAP sync
132   *                        destination plugin transformation.
133   * @param  parser         The argument parser which has been initialized from
134   *                        the configuration for this LDAP sync destination
135   *                        plugin.
136   *
137   * @throws  LDAPException  If a problem occurs while initializing this LDAP
138   *                         sync destination plugin.
139   */
140  public void initializeLDAPSyncDestinationPlugin(
141                   final SyncServerContext serverContext,
142                   final LDAPSyncDestinationPluginConfig config,
143                   final ArgumentParser parser)
144         throws LDAPException
145  {
146    // No initialization will be performed by default.
147  }
148
149
150
151  /**
152   * Performs any cleanup which may be necessary when this LDAP sync destination
153   * plugin is to be taken out of service.
154   */
155  public void finalizeLDAPSyncDestinationPlugin()
156  {
157    // No implementation is required.
158  }
159
160
161
162  /**
163   * {@inheritDoc}
164   */
165  public boolean isConfigurationAcceptable(
166                      final LDAPSyncDestinationPluginConfig config,
167                      final ArgumentParser parser,
168                      final List<String> unacceptableReasons)
169  {
170    // No extended validation will be performed.
171    return true;
172  }
173
174
175
176  /**
177   * {@inheritDoc}
178   */
179  public ResultCode applyConfiguration(
180                         final LDAPSyncDestinationPluginConfig config,
181                         final ArgumentParser parser,
182                         final List<String> adminActionsRequired,
183                         final List<String> messages)
184  {
185    // By default, no configuration changes will be applied.
186    return ResultCode.SUCCESS;
187  }
188
189
190
191  /**
192   * This method is called before a destination entry is fetched.  A
193   * connection to the destination server is provided along with the
194   * {@code SearchRequest} that will be sent to the server.  This method is
195   * overridden by plugins that need to have access to the search request
196   * before it is sent to the destination server.  This includes updating the
197   * search request as well as performing the search instead of the core server,
198   * including doing additional searches.  For plugins that need to manipulate
199   * the entries that the core LDAP Sync Destination code retrieves from the
200   * destination, implementing the {@link #postFetch} method is more natural.
201   * <p>
202   * This method might be called multiple times for a single synchronization
203   * operation, specifically when there are multiple search criteria or
204   * multiple base DNs defined for the Sync Destination.
205   *
206   * @param  destinationConnection  A connection to the destination server.
207   * @param  searchRequest          The search request that the LDAP Sync
208   *                                Destination will use to fetch the entry.
209   * @param  fetchedEntries         A list of entries that have been fetched.
210   *                                When the search criteria matches multiple
211   *                                entries, they should all be returned.  A
212   *                                plugin that wishes to implement the fetch
213   *                                should put the fetched entries here and
214   *                                return
215   *                                {@code PreStepResult#SKIP_CURRENT_STEP}.
216   * @param  operation              The synchronization operation for this
217   *                                change.
218   *
219   * @return  The result of the plugin processing.  Note:
220   *          {@code PreStepResult#SKIP_CURRENT_STEP} should only be returned
221   *          if this plugin takes responsibility for fully fetching the entry
222   *          according to the search request and for populating the
223   *          fetched entry list.
224   *
225   * @throws  LDAPException  In general subclasses should not catch
226   *                         LDAPExceptions that are thrown when
227   *                         using the LDAPInterface unless there
228   *                         are specific exceptions that are
229   *                         expected.  The Data Sync Server
230   *                         will handle LDAPExceptions in an
231   *                         appropriate way based on the specific
232   *                         cause of the exception.  For example,
233   *                         some errors will result in the
234   *                         SyncOperation being retried, and others
235   *                         will trigger fail over to a different
236   *                         server.  Plugins should only throw
237   *                         LDAPException for errors related to
238   *                         communication with the LDAP server.
239   *                         Use the return code to indicate other
240   *                         types of errors, which might require
241   *                         retry.
242   */
243  public PreStepResult preFetch(final LDAPInterface destinationConnection,
244                                final SearchRequest searchRequest,
245                                final List<Entry> fetchedEntries,
246                                final SyncOperation operation)
247       throws LDAPException
248  {
249    return PreStepResult.CONTINUE;
250  }
251
252
253
254  /**
255   * This method is called after an attempt to fetch a destination entry.  An
256   * connection to the destination server is provided along with the
257   * {@code SearchRequest} that was sent to the server.  This method is
258   * overridden by plugins that need to manipulate the search results that
259   * are returned to the Sync Pipe.  This can include filtering out certain
260   * entries, remove information from the entries, or adding additional
261   * information, possibly by doing a followup LDAP search.
262   * <p>
263   * This method might be called multiple times for a single synchronization
264   * operation, specifically when there are multiple search criteria or
265   * multiple base DNs defined for the Sync Destination.
266   * <p>
267   * This method will not be called if the search fails, for instance, if
268   * the base DN of the search does not exist.
269   *
270   * @param  destinationConnection  A connection to the destination server.
271   * @param  searchRequest          The search request that the LDAP Sync
272   *                                Destination used to fetch the entry.
273   * @param  fetchedEntries         A list of entries that have been fetched.
274   *                                When the search criteria matches multiple
275   *                                entries, they will all be returned.  Entries
276   *                                in this list can be edited directly, and the
277   *                                list can be edited as well.
278   * @param  operation              The synchronization operation for this
279   *                                change.
280   *
281   * @return  The result of the plugin processing.
282   *
283   * @throws  LDAPException  In general subclasses should not catch
284   *                         LDAPExceptions that are thrown when
285   *                         using the LDAPInterface unless there
286   *                         are specific exceptions that are
287   *                         expected.  The Data Sync Server
288   *                         will handle LDAPExceptions in an
289   *                         appropriate way based on the specific
290   *                         cause of the exception.  For example,
291   *                         some errors will result in the
292   *                         SyncOperation being retried, and others
293   *                         will trigger fail over to a different
294   *                         server.  Plugins should only throw
295   *                         LDAPException for errors related to
296   *                         communication with the LDAP server.
297   *                         Use the return code to indicate other
298   *                         types of errors, which might require
299   *                         retry.
300   */
301  public PostStepResult postFetch(final LDAPInterface destinationConnection,
302                                  final SearchRequest searchRequest,
303                                  final List<Entry> fetchedEntries,
304                                  final SyncOperation operation)
305       throws LDAPException
306  {
307    return PostStepResult.CONTINUE;
308  }
309
310
311
312  /**
313   * This method is called before a destination entry is created.  A
314   * connection to the destination server is provided along with the
315   * {@code Entry} that will be sent to the server.  This method is
316   * overridden by plugins that need to alter the entry before it is created
317   * at the server.
318   *
319   * @param  destinationConnection  A connection to the destination server.
320   * @param  entryToCreate          The entry that will be created at the
321   *                                destination.  A plugin that wishes to
322   *                                create the entry should be sure to return
323   *                                {@code PreStepResult#SKIP_CURRENT_STEP}.
324   * @param  operation              The synchronization operation for this
325   *                                change.
326   *
327   * @return  The result of the plugin processing.
328   *
329   * @throws  LDAPException  In general subclasses should not catch
330   *                         LDAPExceptions that are thrown when
331   *                         using the LDAPInterface unless there
332   *                         are specific exceptions that are
333   *                         expected.  The Data Sync Server
334   *                         will handle LDAPExceptions in an
335   *                         appropriate way based on the specific
336   *                         cause of the exception.  For example,
337   *                         some errors will result in the
338   *                         SyncOperation being retried, and others
339   *                         will trigger fail over to a different
340   *                         server.  Plugins should only throw
341   *                         LDAPException for errors related to
342   *                         communication with the LDAP server.
343   *                         Use the return code to indicate other
344   *                         types of errors, which might require
345   *                         retry.
346   */
347  public PreStepResult preCreate(final LDAPInterface destinationConnection,
348                                 final Entry entryToCreate,
349                                 final SyncOperation operation)
350       throws LDAPException
351  {
352    return PreStepResult.CONTINUE;
353  }
354
355
356
357  /**
358   * This method is called before a destination entry is modified.  A
359   * connection to the destination server is provided along with the
360   * {@code Entry} that will be sent to the server.  This method is
361   * overridden by plugins that need to perform some processing on an entry
362   * before it is modified.
363   *
364   * @param  destinationConnection  A connection to the destination server.
365   * @param  entryToModify          The entry that will be modified at the
366   *                                destination.  A plugin that wishes to
367   *                                modify the entry should be sure to return
368   *                                {@code PreStepResult#SKIP_CURRENT_STEP}.
369   * @param  modsToApply            A modifiable list of the modifications to
370   *                                apply at the server.
371   * @param  operation              The synchronization operation for this
372   *                                change.
373   *
374   * @return  The result of the plugin processing.
375   *
376   * @throws  LDAPException  In general subclasses should not catch
377   *                         LDAPExceptions that are thrown when
378   *                         using the LDAPInterface unless there
379   *                         are specific exceptions that are
380   *                         expected.  The Data Sync Server
381   *                         will handle LDAPExceptions in an
382   *                         appropriate way based on the specific
383   *                         cause of the exception.  For example,
384   *                         some errors will result in the
385   *                         SyncOperation being retried, and others
386   *                         will trigger fail over to a different
387   *                         server.  Plugins should only throw
388   *                         LDAPException for errors related to
389   *                         communication with the LDAP server.
390   *                         Use the return code to indicate other
391   *                         types of errors, which might require
392   *                         retry.
393   */
394  public PreStepResult preModify(final LDAPInterface destinationConnection,
395                                 final Entry entryToModify,
396                                 final List<Modification> modsToApply,
397                                 final SyncOperation operation)
398       throws LDAPException
399  {
400    return PreStepResult.CONTINUE;
401  }
402
403
404
405  /**
406   * This method is called before a destination entry is deleted.  A
407   * connection to the destination server is provided along with the
408   * {@code Entry} that will be sent to the server.  This method is
409   * overridden by plugins that need to perform some processing on an entry
410   * before it is deleted.  A plugin could choose to mark an entry as disabled
411   * instead of deleting it for instance, or move the entry to a different
412   * part of the directory hierarchy.
413   *
414   * @param  destinationConnection  A connection to the destination server.
415   * @param  entryToDelete          The entry that will be deleted at the
416   *                                destination.  A plugin that wishes to
417   *                                delete the entry should be sure to return
418   *                                {@code PreStepResult#SKIP_CURRENT_STEP}.
419   * @param  operation              The synchronization operation for this
420   *                                change.
421   *
422   * @return  The result of the plugin processing.
423   *
424   * @throws  LDAPException  In general subclasses should not catch
425   *                         LDAPExceptions that are thrown when
426   *                         using the LDAPInterface unless there
427   *                         are specific exceptions that are
428   *                         expected.  The Data Sync Server
429   *                         will handle LDAPExceptions in an
430   *                         appropriate way based on the specific
431   *                         cause of the exception.  For example,
432   *                         some errors will result in the
433   *                         SyncOperation being retried, and others
434   *                         will trigger fail over to a different
435   *                         server.  Plugins should only throw
436   *                         LDAPException for errors related to
437   *                         communication with the LDAP server.
438   *                         Use the return code to indicate other
439   *                         types of errors, which might require
440   *                         retry.
441   */
442  public PreStepResult preDelete(final LDAPInterface destinationConnection,
443                                 final Entry entryToDelete,
444                                 final SyncOperation operation)
445       throws LDAPException
446  {
447    return PreStepResult.CONTINUE;
448  }
449
450
451
452  /**
453   * This method is called prior to executing any add, modify, delete, or
454   * search from the destination but after the respective pre method (e.g
455   * preFetch or preModify). A connection to the destination server is provided
456   * along with the {@code UpdatableLDAPRequest} that will be sent to the
457   * server. this method is overridden by plugins that need to modify the
458   * LDAP request prior to execution. For example, attaching a {@code Control}
459   * to the request. Callers of this method can use {@code instanceof}
460   * to determine which type of LDAP request is being made.
461   *
462   * @param destinationConnection A connection to the destination server.
463   * @param request               The LDAP request that will be sent to
464   *                              the destination server.
465   * @param operation             The synchronization operation for this
466   *                              change.
467   *
468   * @return  The result of the plugin processing. Be very careful when
469   *          returning {@code PreStepResult#RETRY_OPERATION_UNLIMITED} as this
470   *          can stall all in flight operations until this operation completes.
471   *          This return value should only be used in situations where a
472   *          remote service (e.g., the LDAP server) is unavailable. In this
473   *          case, it's preferable to just throw the underlying LDAPException,
474   *          which the Data Sync Server will handle correctly based on
475   *          the type of the operation.
476   *
477   * @throws  LDAPException  In general subclasses should not catch
478   *                         LDAPExceptions that are thrown when
479   *                         using the LDAPInterface unless there
480   *                         are specific exceptions that are
481   *                         expected.  The Data Sync Server
482   *                         will handle LDAPExceptions in an
483   *                         appropriate way based on the specific
484   *                         cause of the exception.  For example,
485   *                         some errors will result in the
486   *                         SyncOperation being retried, and others
487   *                         will trigger fail over to a different
488   *                         server.  Plugins should only throw
489   *                         LDAPException for errors related to
490   *                         communication with the LDAP server.
491   *                         Use the return code to indicate other
492   *                         types of errors, which might require
493   *                         retry.
494   */
495  public PreStepResult transformRequest(
496          final LDAPInterface destinationConnection,
497          final UpdatableLDAPRequest request,
498          final SyncOperation operation)
499          throws LDAPException
500  {
501    return PreStepResult.CONTINUE;
502  }
503}