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
029import java.util.List;
030
031import com.unboundid.directory.sdk.common.internal.Configurable;
032import com.unboundid.directory.sdk.sync.config.SyncDestinationConfig;
033import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension;
034import com.unboundid.directory.sdk.sync.types.EndpointException;
035import com.unboundid.directory.sdk.sync.types.SyncOperation;
036import com.unboundid.directory.sdk.sync.types.SyncServerContext;
037import com.unboundid.ldap.sdk.Entry;
038import com.unboundid.ldap.sdk.Modification;
039import com.unboundid.util.Extensible;
040import com.unboundid.util.ThreadSafety;
041import com.unboundid.util.ThreadSafetyLevel;
042import com.unboundid.util.args.ArgumentException;
043import com.unboundid.util.args.ArgumentParser;
044
045/**
046 * This class defines an API that must be implemented by scripted extensions
047 * that wish to push changes processed by the Data Sync Server to an
048 * arbitrary destination. This type of sync destination is generic and can
049 * support a wide range of endpoints. In addition, this type of sync destination
050 * supports one-way notifications, where the source and destination entries are
051 * never compared but instead changes are pushed straight through.
052 *
053 * <H2>Configuring Scripted Sync Destinations</H2>
054 * In order to configure a sync destination created using this API, use
055 * a command like:
056 * <PRE>
057 *      dsconfig create-sync-destination \
058 *           --sync-destination-name "<I>{name}</I>" \
059 *           --type groovy-scripted \
060 *           --set "script-class:<I>{class-name}</I>" \
061 *           --set "script-argument:<I>{name=value}</I>"
062 * </PRE>
063 * where "<I>{name}</I>" is the name to use for the sync destination
064 * instance, "<I>{script-name}</I>" is the fully-qualified name of the Java
065 * class that extends
066 * {@code com.unboundid.directory.sdk.sync.scripting.ScriptedSyncDestination},
067 * and "<I>{name=value}</I>" represents name-value pairs for any arguments to
068 * provide to the sync destination. If multiple arguments should be
069 * provided to extension, then the
070 * "<CODE>--set script-argument:<I>{name=value}</I></CODE>" option should be
071 * provided multiple times.
072 *
073 * @see
074 *    com.unboundid.directory.sdk.sync.api.SyncDestination
075 */
076@Extensible()
077@SynchronizationServerExtension(appliesToLocalContent=false,
078                                appliesToSynchronizedContent=true)
079@ThreadSafety(level = ThreadSafetyLevel.MOSTLY_THREADSAFE)
080public abstract class ScriptedSyncDestination implements Configurable
081{
082
083  /**
084   * {@inheritDoc}
085   */
086  public void defineConfigArguments(final ArgumentParser parser)
087         throws ArgumentException
088  {
089    // No arguments will be allowed by default.
090  }
091
092
093
094  /**
095   * Initializes this sync destination. This is called when a Sync Pipe
096   * first starts up, or when the <i>resync</i> process first starts up. Any
097   * initialization should be performed here. This method should generally store
098   * the {@link SyncServerContext} in a class member so that it can be used
099   * elsewhere in the implementation.
100   * <p>
101   * The default implementation is empty.
102   *
103   * @param  serverContext  A handle to the server context for the server in
104   *                        which this extension is running. Extensions should
105   *                        typically store this in a class member.
106   * @param  config         The general configuration for this object.
107   * @param  parser         The argument parser which has been initialized from
108   *                        the configuration for this sync destination.
109   * @throws  EndpointException
110   *                        if a problem occurs while initializing this
111   *                        sync destination.
112   */
113  public void initializeSyncDestination(
114                                     final SyncServerContext serverContext,
115                                     final SyncDestinationConfig config,
116                                     final ArgumentParser parser)
117                                             throws EndpointException
118  {
119    // No initialization will be performed by default.
120  }
121
122
123
124  /**
125   * This hook is called when a Sync Pipe shuts down, or when the <i>resync</i>
126   * process shuts down. Any clean-up of this sync destination should be
127   * performed here.
128   * <p>
129   * The default implementation is empty.
130   */
131  public void finalizeSyncDestination()
132  {
133    // No implementation is performed by default.
134  }
135
136
137
138  /**
139   * Return the URL or path identifying the destination endpoint
140   * to which this extension is transmitting data. This is used for logging
141   * purposes only, so it could just be a server name or hostname and port, etc.
142   *
143   * @return the path to the destination endpoint
144   */
145  public abstract String getCurrentEndpointURL();
146
147
148
149  /**
150   * Return a full destination entry (in LDAP form) from the destination
151   * endpoint, corresponding to the source {@link Entry} that is passed in.
152   * This method should perform any queries necessary to gather the latest
153   * values for all the attributes to be synchronized and return them in an
154   * Entry.
155   * <p>
156   * This method only needs to be implemented if the 'synchronization-mode' on
157   * the Sync Pipe is set to 'standard'. If it is set to 'notification', this
158   * method will never be called, and the pipe will pass changes straight
159   * through to one of {@link #createEntry}, {@link #modifyEntry}, or
160   * {@link #deleteEntry}.
161   * <p>
162   * Note that the if the source entry was renamed (see
163   * {@link SyncOperation#isModifyDN}), the
164   * <code>destEntryMappedFromSrc</code> will have the new DN; the old DN can
165   * be obtained by calling
166   * {@link SyncOperation#getDestinationEntryBeforeChange()} and getting the DN
167   * from there. This method should return the entry in its existing form
168   * (i.e. with the old DN, before it is changed).
169   * <p>
170   * This method <b>must be thread safe</b>, as it will be called repeatedly and
171   * concurrently by each of the Sync Pipe worker threads as they process
172   * entries.
173   * @param destEntryMappedFromSrc
174   *          the LDAP entry which corresponds to the destination "entry" to
175   *          fetch
176   * @param  operation
177   *          the sync operation for this change
178   * @return a list containing the full LDAP entries that matched this search
179   *          (there may be more than one), or an empty list if no such entry
180   *          exists
181   * @throws EndpointException
182   *           if there is an error fetching the entry
183   */
184  public List<Entry> fetchEntry(final Entry destEntryMappedFromSrc,
185                                final SyncOperation operation)
186                                    throws EndpointException
187  {
188    throw new UnsupportedOperationException(
189                 "The fetchEntry() method must be implemented in the " +
190                 "ScriptedSyncDestination if the Sync Pipe is " +
191                 "running in standard mode (see 'synchronization-mode' " +
192                 "in the Sync Pipe configuration).");
193  }
194
195
196
197  /**
198   * Creates a full destination "entry", corresponding to the LDAP
199   * {@link Entry} that is passed in. This method is responsible for
200   * transforming the contents of the entry into the desired format and
201   * transmitting it to the target destination. It should perform any inserts or
202   * updates necessary to make sure the entry is fully created on the
203   * destination endpoint.
204   * <p>
205   * This method <b>must be thread safe</b>, as it will be called repeatedly and
206   * concurrently by the Sync Pipe worker threads as they process CREATE
207   * operations.
208   * @param entryToCreate
209   *          the LDAP entry which corresponds to the destination
210   *          "entry" to create
211   * @param  operation
212   *          the sync operation for this change
213   * @throws EndpointException
214   *           if there is an error creating the entry
215   */
216  public abstract void createEntry(final Entry entryToCreate,
217                                   final SyncOperation operation)
218                                      throws EndpointException;
219
220
221
222  /**
223   * Modify an "entry" on the destination, corresponding to the LDAP
224   * {@link Entry} that is passed in. This method is responsible for
225   * transforming the contents of the entry into the desired format and
226   * transmitting it to the target destination. It may perform multiple updates
227   * (including inserting or deleting other attributes) in order to fully
228   * synchronize the entire entry on the destination endpoint.
229   * <p>
230   * Note that the if the source entry was renamed (see
231   * {@link SyncOperation#isModifyDN}), the
232   * <code>fetchedDestEntry</code> will have the old DN; the new DN can
233   * be obtained by calling
234   * {@link SyncOperation#getDestinationEntryAfterChange()} and getting the DN
235   * from there.
236   * <p>
237   * This method <b>must be thread safe</b>, as it will be called repeatedly and
238   * concurrently by the Sync Pipe worker threads as they process MODIFY
239   * operations.
240   * @param entryToModify
241   *          the LDAP entry which corresponds to the destination
242   *          "entry" to modify. If the synchronization mode is 'standard',
243   *          this will be the entry that was returned by {@link #fetchEntry};
244   *          otherwise if the synchronization mode is 'notification', this
245   *          will be the mapped destination entry.
246   * @param modsToApply
247   *          a list of Modification objects which should be applied
248   * @param  operation
249   *          the sync operation for this change
250   * @throws EndpointException
251   *           if there is an error modifying the entry
252   */
253  public abstract void modifyEntry(final Entry entryToModify,
254                                   final List<Modification> modsToApply,
255                                   final SyncOperation operation)
256                                          throws EndpointException;
257
258
259
260  /**
261   * Delete a full "entry" from the destination, corresponding to the LDAP
262   * {@link Entry} that is passed in. This method may perform multiple deletes
263   * or updates if necessary to fully delete the entry from the destination
264   * endpoint.
265   * <p>
266   * This method <b>must be thread safe</b>, as it will be called repeatedly and
267   * concurrently by the Sync Pipe worker threads as they process DELETE
268   * operations.
269   * @param entryToDelete
270   *          the LDAP entry which corresponds to the destination
271   *          "entry" to delete. If the synchronization mode is 'standard',
272   *          this will be the entry that was returned by {@link #fetchEntry};
273   *          otherwise if the synchronization mode is 'notification', this
274   *          will be the mapped destination entry.
275   * @param  operation
276   *          the sync operation for this change
277   * @throws EndpointException
278   *           if there is an error deleting the entry
279   */
280  public abstract void deleteEntry(final Entry entryToDelete,
281                                   final SyncOperation operation)
282                                        throws EndpointException;
283
284}