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 *      Portions Copyright 2010-2024 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.SyncPipePluginConfig;
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.ResultCode;
043import com.unboundid.util.Extensible;
044import com.unboundid.util.ThreadSafety;
045import com.unboundid.util.ThreadSafetyLevel;
046import com.unboundid.util.args.ArgumentException;
047import com.unboundid.util.args.ArgumentParser;
048
049
050
051/**
052 * This class defines an API that must be implemented by scripted extensions
053 * that perform processing on synchronization operations within the Sync Pipe.
054 * These extensions may be used to
055 * <ul>
056 *   <li>Filter out certain changes from being synchronized.</li>
057 *   <li>Add and remove attributes that should be synchronized with the
058 *       destination independent of whether they changed at the source or
059 *       not.</li>
060 *   <li>Manipulate the changes that are synchronized to ignore certain
061 *       modified attributes or change the representation of modified
062 *       attributes.</li>
063 *   <li>Skip certain steps in Sync Pipe processing, e.g. attribute
064 *       and DN mapping.</li>
065 * </ul>
066 * Most plugins will need to override the {@code postMapping} method but not
067 * the {@code preMapping} method.  These extensions do not have access to the
068 * Sync Source or Sync Destination.
069 * <BR>
070 * <H2>Configuring Groovy-Scripted Sync Pipe Plugins</H2>
071 * In order to configure a scripted sync pipe plugin based on this API and
072 * written in the Groovy scripting language, use a command like:
073 * <PRE>
074 *      dsconfig create-sync-pipe-plugin \
075 *           --plugin-name "<I>{plugin-name}</I>" \
076 *           --type groovy-scripted \
077 *           --set "script-class:<I>{class-name}</I>" \
078 *           --set "script-argument:<I>{name=value}</I>"
079 * </PRE>
080 * where "<I>{plugin-name}</I>" is the name to use for the sync pipe plugin
081 * instance, "<I>{class-name}</I>" is the fully-qualified name of the Groovy
082 * class written using this API, and "<I>{name=value}</I>" represents name-value
083 * pairs for any arguments to provide to the sync pipe plugin.  If multiple
084 * arguments should be provided to the sync pipe plugin, then the
085 * "<CODE>--set script-argument:<I>{name=value}</I></CODE>" option should be
086 * provided multiple times.
087 *
088 * @see  com.unboundid.directory.sdk.sync.api.SyncPipePlugin
089 */
090@Extensible()
091@SynchronizationServerExtension(appliesToLocalContent=false,
092     appliesToSynchronizedContent=true)
093@ThreadSafety(level= ThreadSafetyLevel.INTERFACE_THREADSAFE)
094public abstract class ScriptedSyncPipePlugin
095       implements Reconfigurable<SyncPipePluginConfig>
096{
097  /**
098   * Creates a new instance of this sync pipe plugin.  All sync pipe
099   * plugin implementations must include a default constructor, but any
100   * initialization should generally be done in the
101   * {@code initializeSyncPipePlugin} method.
102   */
103  public ScriptedSyncPipePlugin()
104  {
105    // No implementation is required.
106  }
107
108
109
110  /**
111   * {@inheritDoc}
112   */
113  public void defineConfigArguments(final ArgumentParser parser)
114         throws ArgumentException
115  {
116    // No arguments will be allowed by default.
117  }
118
119
120
121  /**
122   * Initializes this sync pipe plugin.
123   *
124   * @param  serverContext  A handle to the server context for the server in
125   *                        which this extension is running.
126   * @param  config         The general configuration for this sync pipe plugin
127   *                        transformation.
128   * @param  parser         The argument parser which has been initialized from
129   *                        the configuration for this sync pipe plugin.
130   *
131   * @throws  LDAPException  If a problem occurs while initializing this sync
132   *                         pipe plugin.
133   */
134  public void initializeSyncPipePlugin(
135                   final SyncServerContext serverContext,
136                   final SyncPipePluginConfig config,
137                   final ArgumentParser parser)
138         throws LDAPException
139  {
140    // No initialization will be performed by default.
141  }
142
143
144
145  /**
146   * Performs any cleanup which may be necessary when this sync pipe plugin
147   * is to be taken out of service.
148   */
149  public void finalizeSyncPipePlugin()
150  {
151    // No implementation is required.
152  }
153
154
155
156  /**
157   * {@inheritDoc}
158   */
159  public boolean isConfigurationAcceptable(final SyncPipePluginConfig config,
160                      final ArgumentParser parser,
161                      final List<String> unacceptableReasons)
162  {
163    // No extended validation will be performed.
164    return true;
165  }
166
167
168
169  /**
170   * {@inheritDoc}
171   */
172  public ResultCode applyConfiguration(final SyncPipePluginConfig config,
173                                       final ArgumentParser parser,
174                                       final List<String> adminActionsRequired,
175                                       final List<String> messages)
176  {
177    // By default, no configuration changes will be applied.
178    return ResultCode.SUCCESS;
179  }
180
181
182
183  /**
184   * This method is called immediately before the attributes and DN in
185   * the source entry are mapped into the equivalent destination entry.
186   * This equivalent destination entry is then compared to the actual
187   * destination entry to determine which of the modified attributes need
188   * to be updated.
189   * <p>
190   * This method is typically used to either filter out certain changes
191   * (by returning {@link PreStepResult#ABORT_OPERATION}) or to manipulate the
192   * source entry before it is converted into an equivalent destination entry.
193   * Attributes that will not otherwise be affected by attribute mapping
194   * can be set in {@code equivalentDestinationEntry}.  Although, due to the
195   * possibility of being overwritten in the mapping phase, manipulation of
196   * {@code equivalentDestinationEntry} is typically reserved for the
197   * {@link #postMapping} method.
198   * <p>
199   * The set of source attributes that should be synchronized at the destination
200   * can be manipulated by calling
201   * {@link SyncOperation#addModifiedSourceAttribute} and
202   * {@link SyncOperation#removeModifiedSourceAttribute} on
203   * {@code operation}.
204   * <p>
205   * Additional steps must be taken if this plugin adds destination attributes
206   * in {@code equivalentDestinationEntry} that need to be modified at the
207   * destination.  For operations with an operation type of
208   * {@link com.unboundid.directory.sdk.sync.types.SyncOperationType#CREATE},
209   * any updates made to
210   * {@code equivalentDestinationEntry} will be included in the
211   * entry created at the destination.  However, for operations with an
212   * operation type of
213   * {@link com.unboundid.directory.sdk.sync.types.SyncOperationType#MODIFY},
214   * destination attributes
215   * added by this plugin that need to be modified must be updated
216   * explicitly by calling
217   * {@link SyncOperation#addModifiedDestinationAttribute}.
218   * <p>
219   * With the exception of aborting changes or skipping the mapping step
220   * completely, most plugins will not need to override this method since
221   * the {@link #postMapping} method has access to the fully mapped destination
222   * entry.
223   *
224   * @param  sourceEntry                 The entry that was fetched from the
225   *                                     source.
226   * @param  equivalentDestinationEntry  The destination entry that is
227   *                                     equivalent to the source.  This entry
228   *                                     will be empty except for any
229   *                                     modifications that have been performed
230   *                                     by other sync pipe plugins.
231   * @param  operation                   The operation that is being
232   *                                     synchronized.
233   *
234   * @return  The result of the plugin processing.  Note:
235   *          {@code PreStepResult#SKIP_CURRENT_STEP} should only be returned
236   *          if this plugin takes responsibility for fully constructing the
237   *          equivalent destination entry.
238   */
239  public PreStepResult preMapping(final Entry sourceEntry,
240                                  final Entry equivalentDestinationEntry,
241                                  final SyncOperation operation)
242  {
243    return PreStepResult.CONTINUE;
244  }
245
246
247
248  /**
249   * This method is called immediately after the attributes and DN in
250   * the source entry are mapped into the equivalent destination entry.
251   * Once this mapping is complete, this equivalent destination entry is then
252   * compared to the actual destination entry to determine which of the modified
253   * attributes need to be updated.
254   * <p>
255   * This method is typically used to manipulate the equivalent destination
256   * entry before these necessary changes are calculated.  It can also be used
257   * to filter out certain changes (by returning
258   * {@link PostStepResult#ABORT_OPERATION}), but this is typically done in
259   * the {@link #preMapping method}.
260   * <p>
261   * The set of source attributes that should be synchronized at the destination
262   * can be manipulated by calling
263   * {@link SyncOperation#addModifiedSourceAttribute} and
264   * {@link SyncOperation#removeModifiedSourceAttribute} on
265   * {@code operation}.
266   * <p>
267   * Additional steps must be taken if this plugin adds destination attributes
268   * in {@code equivalentDestinationEntry} that need to be modified at the
269   * destination.  For operations with an operation type of
270   * {@link com.unboundid.directory.sdk.sync.types.SyncOperationType#CREATE},
271   * any updates made to
272   * {@code equivalentDestinationEntry} will be included in the
273   * entry created at the destination.  However, for operations with an
274   * operation type of
275   * {@link com.unboundid.directory.sdk.sync.types.SyncOperationType#MODIFY},
276   * destination attributes
277   * added by this plugin that need to be modified must be updated
278   * explicitly by calling
279   * {@link SyncOperation#addModifiedDestinationAttribute}.
280   * <p>
281   * With the exception of aborting changes or skipping the mapping step
282   * completely, most plugins will override this method instead of
283   * {@link #preMapping} because this method has access to the fully mapped
284   * destination entry.
285   *
286   * @param  sourceEntry                 The entry that was fetched from the
287   *                                     source.
288   * @param  equivalentDestinationEntry  The destination entry that is
289   *                                     equivalent to the source.  This entry
290   *                                     will include all attributes mapped
291   *                                     from the source entry.
292   * @param  operation                   The operation that is being
293   *                                     synchronized.
294   *
295   * @return  The result of the plugin processing.
296   */
297  public PostStepResult postMapping(final Entry sourceEntry,
298                                    final Entry equivalentDestinationEntry,
299                                    final SyncOperation operation)
300  {
301    return PostStepResult.CONTINUE;
302  }
303}