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