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.api;
028
029
030
031import java.sql.SQLException;
032import java.util.Collections;
033import java.util.List;
034import java.util.Map;
035
036import com.unboundid.directory.sdk.common.internal.Configurable;
037import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider;
038import com.unboundid.directory.sdk.common.internal.UnboundIDExtension;
039import com.unboundid.directory.sdk.sync.config.JDBCSyncDestinationConfig;
040import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension;
041import com.unboundid.directory.sdk.sync.types.SyncOperation;
042import com.unboundid.directory.sdk.sync.types.SyncServerContext;
043import com.unboundid.directory.sdk.sync.types.TransactionContext;
044import com.unboundid.ldap.sdk.Entry;
045import com.unboundid.ldap.sdk.Modification;
046import com.unboundid.util.Extensible;
047import com.unboundid.util.ThreadSafety;
048import com.unboundid.util.ThreadSafetyLevel;
049import com.unboundid.util.args.ArgumentException;
050import com.unboundid.util.args.ArgumentParser;
051
052
053
054/**
055 * This class defines an API that must be implemented by extensions
056 * in order to synchronize data into a relational database. Since the
057 * Ping Identity Data Sync Server is LDAP-centric, this API allows you to take
058 * LDAP entries and split them out into database content and make the
059 * appropriate updates. The lifecycle of a sync operation is as follows:
060 * <ol>
061 * <li>Detect change at the synchronization source</li>
062 * <li>Fetch full source entry</li>
063 * <li>Perform any mappings and compute the equivalent destination entry</li>
064 * <li>Fetch full destination entry</li>
065 * <li>Diff the computed destination entry and actual destination entry</li>
066 * <li>Apply the minimal set of changes at the destination to bring it in sync
067 * </li>
068 * </ol>
069 * This implies that the {@link #fetchEntry(TransactionContext, Entry,
070 * SyncOperation)} method will
071 * always be called once prior to any of the other methods in this class.
072 * <p>
073 * In several places a {@link TransactionContext} is provided, which allows
074 * controlled access to the target database. By default, methods in this class
075 * are always provided with a fresh connection (i.e. a new transaction), and the
076 * Data Sync Server will always commit or rollback the transaction
077 * automatically, depending on how the method returns. If a method call returns
078 * successfully, then the transaction will be committed. If an exception is
079 * thrown, then the transaction will be rolled back. In rare situations, it
080 * might be necessary for an implementation to perform its own commit or
081 * rollback of transactions by calling methods on {@link TransactionContext}.
082 * <p>
083 * Several of these methods throw {@link SQLException}, which should be used in
084 * the case of any database access error. For other types of errors, runtime
085 * exceptions may be used (IllegalStateException, NullPointerException, etc.).
086 * The Data Sync Server will automatically retry operations that fail,
087 * up to a configurable amount of attempts. The exception to this rule is if a
088 * SQLException is thrown with a SQL state string beginning with "08"; this
089 * indicates a connection error, and in this case the operation is retried
090 * indefinitely.  For these reasons implementers should refrain from handling
091 * or wrapping any {@link SQLException} and instead let it be handled by the
092 * calling code.
093 * <BR>
094 * <H2>Configuring JDBC Sync Destinations</H2>
095 * In order to configure a JDBC sync destination based on this API and
096 * written in Java, use a command like:
097 * <PRE>
098 *      dsconfig create-sync-destination \
099 *           --destination-name "<I>{destination-name}</I>" \
100 *           --type third-party-jdbc \
101 *           --set "server:{server-name}" \
102 *           --set "extension-class:<I>{class-name}</I>" \
103 *           --set "extension-argument:<I>{name=value}</I>"
104 * </PRE>
105 * where "<I>{destination-name}</I>" is the name to use for the JDBC sync
106 * destination instance, "<I>{server-name}</I>" is the name of the JDBC external
107 * server that will be used as the sync destination, "<I>{class-name}</I>" is
108 * the fully-qualified name of the Java class written using this API, and
109 * "<I>{name=value}</I>" represents name-value pairs for any arguments to
110 * provide to the JDBC sync destination.  If multiple arguments should be
111 * provided to the JDBC sync destination, then the
112 * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be
113 * provided multiple times.
114 */
115@Extensible()
116@SynchronizationServerExtension(appliesToLocalContent=false,
117     appliesToSynchronizedContent=true)
118public abstract class JDBCSyncDestination implements UnboundIDExtension,
119                                                     Configurable,
120                                                     ExampleUsageProvider
121{
122  /**
123   * Creates a new instance of this JDBC Sync Destination.  All sync
124   * destination implementations must include a default constructor, but any
125   * initialization should generally be done in the
126   * {@link #initializeJDBCSyncDestination} method.
127   */
128  public JDBCSyncDestination()
129  {
130    //no implementation is required
131  }
132
133
134
135  /**
136   * {@inheritDoc}
137   */
138  public abstract String getExtensionName();
139
140
141
142  /**
143   * {@inheritDoc}
144   */
145  public abstract String[] getExtensionDescription();
146
147
148
149  /**
150   * {@inheritDoc}
151   */
152  public Map<List<String>,String> getExamplesArgumentSets()
153  {
154    return Collections.emptyMap();
155  }
156
157
158
159  /**
160   * {@inheritDoc}
161   */
162  public void defineConfigArguments(final ArgumentParser parser)
163         throws ArgumentException
164  {
165    // No arguments will be allowed by default.
166  }
167
168
169
170  /**
171   * This hook is called when a Sync Pipe first starts up, or when the
172   * <i>resync</i> process first starts up. Any initialization of this sync
173   * destination should be performed here. This method should generally store
174   * the {@link SyncServerContext} in a class
175   * member so that it can be used elsewhere in the implementation.
176   * <p>
177   * A {@link TransactionContext} is provided, which allows
178   * controlled access to the target database. The context will contain a fresh
179   * fresh connection (i.e. a new transaction), and the Data Sync Server
180   * will always commit or rollback the transaction automatically, depending on
181   * whether this method returns normally or throws an exception. See the class
182   * level documentation for warnings and additional details.
183   * <p>
184   * The default implementation is empty.
185   *
186   * @param ctx
187   *          a TransactionContext which provides a valid JDBC connection to the
188   *          database.
189   * @param  serverContext  A handle to the server context for the server in
190   *                        which this extension is running.
191   * @param  config         The general configuration for this sync destination.
192   * @param  parser         The argument parser which has been initialized from
193   *                        the configuration for this JDBC sync destination.
194   */
195  @ThreadSafety(level = ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
196  public void initializeJDBCSyncDestination(
197                                         final TransactionContext ctx,
198                                         final SyncServerContext serverContext,
199                                         final JDBCSyncDestinationConfig config,
200                                         final ArgumentParser parser)
201  {
202    // No initialization will be performed by default.
203  }
204
205
206
207  /**
208   * This hook is called when a Sync Pipe shuts down, or when the <i>resync</i>
209   * process shuts down. Any clean-up of this sync destination should be
210   * performed here.
211   * <p>
212   * A {@link TransactionContext} is provided, which allows
213   * controlled access to the target database. The context will contain a fresh
214   * fresh connection (i.e. a new transaction), and the Data Sync Server
215   * will always commit or rollback the transaction automatically, depending on
216   * whether this method returns normally or throws an exception. See the class
217   * level documentation for warnings and additional details.
218   * <p>
219   * The default implementation is empty.
220   *
221   * @param ctx
222   *          a TransactionContext which provides a valid JDBC connection to the
223   *          database.
224   */
225  @ThreadSafety(level = ThreadSafetyLevel.METHOD_NOT_THREADSAFE)
226  public void finalizeJDBCSyncDestination(
227                                      final TransactionContext ctx)
228  {
229    //No implementation required by default.
230  }
231
232
233
234  /**
235   * Return a full destination entry (in LDAP form) from the database,
236   * corresponding to the source {@link Entry} that is passed in.
237   * This method should perform any queries necessary to gather the latest
238   * values for all the attributes to be synchronized and return them in an
239   * Entry.
240   * <p>
241   * Note that the if the source entry was renamed (see
242   * {@link SyncOperation#isModifyDN}), the
243   * <code>destEntryMappedFromSrc</code> will have the new DN; the old DN can
244   * be obtained by calling
245   * {@link SyncOperation#getDestinationEntryBeforeChange()} and getting the DN
246   * from there. This method should return the entry in its existing form
247   * (i.e. with the old DN, before it is changed).
248   * <p>
249   * A {@link TransactionContext} is provided, which allows
250   * controlled access to the target database. The context will contain a fresh
251   * fresh connection (i.e. a new transaction), and the Data Sync Server
252   * will always commit or rollback the transaction automatically, depending on
253   * whether this method returns normally or throws an exception. See the class
254   * level documentation for warnings and additional details.
255   * <p>
256   * This method <b>must be thread safe</b>, as it will be called repeatedly and
257   * concurrently by each of the Sync Pipe worker threads as they process
258   * entries.
259   *
260   * @param ctx
261   *          a TransactionContext which provides a valid JDBC connection to the
262   *          database.
263   * @param destEntryMappedFromSrc
264   *          the LDAP entry which corresponds to the database "entry" to fetch
265   * @param  operation
266   *          the sync operation for this change
267   * @return a full LDAP Entry, or null if no such entry exists.
268   * @throws SQLException
269   *           if there is an error fetching the entry
270   */
271  @ThreadSafety(level = ThreadSafetyLevel.METHOD_THREADSAFE)
272  public abstract Entry fetchEntry(final TransactionContext ctx,
273                                   final Entry destEntryMappedFromSrc,
274                                   final SyncOperation operation)
275                                              throws SQLException;
276
277
278
279  /**
280   * Creates a full database "entry", corresponding to the LDAP
281   * {@link Entry} that is passed in. This method should perform any inserts and
282   * updates necessary to make sure the entry is fully created on the database.
283   * <p>
284   * A {@link TransactionContext} is provided, which allows
285   * controlled access to the target database. The context will contain a fresh
286   * fresh connection (i.e. a new transaction), and the Data Sync Server
287   * will always commit or rollback the transaction automatically, depending on
288   * whether this method returns normally or throws an exception. See the class
289   * level documentation for warnings and additional details.
290   * <p>
291   * This method <b>must be thread safe</b>, as it will be called repeatedly and
292   * concurrently by the Sync Pipe worker threads as they process CREATE
293   * operations.
294   *
295   * @param ctx
296   *          a TransactionContext which provides a valid JDBC connection to the
297   *          database.
298   * @param entryToCreate
299   *          the LDAP entry which corresponds to the database "entry" to create
300   * @param  operation
301   *          the sync operation for this change
302   * @throws SQLException
303   *           if there is an error creating the entry
304   */
305  @ThreadSafety(level = ThreadSafetyLevel.METHOD_THREADSAFE)
306  public abstract void createEntry(final TransactionContext ctx,
307                                   final Entry entryToCreate,
308                                   final SyncOperation operation)
309                                      throws SQLException;
310
311
312
313  /**
314   * Modify an "entry" in the database, corresponding to the LDAP
315   * {@link Entry} that is passed in. This method may perform multiple updates
316   * (including inserting or deleting rows) in order to fully synchronize the
317   * entire entry on the database.
318   * <p>
319   * Note that the if the source entry was renamed (see
320   * {@link SyncOperation#isModifyDN}), the <code>fetchedDestEntry</code> will
321   * have the old DN; the new DN can be obtained by calling
322   * {@link SyncOperation#getDestinationEntryAfterChange()} and getting the DN
323   * from there.
324   * <p>
325   * A {@link TransactionContext} is provided, which allows
326   * controlled access to the target database. The context will contain a fresh
327   * fresh connection (i.e. a new transaction), and the Data Sync Server
328   * will always commit or rollback the transaction automatically, depending on
329   * whether this method returns normally or throws an exception. See the class
330   * level documentation for warnings and additional details.
331   * <p>
332   * This method <b>must be thread safe</b>, as it will be called repeatedly and
333   * concurrently by the Sync Pipe worker threads as they process MODIFY
334   * operations.
335   * @param ctx
336   *          a TransactionContext which provides a valid JDBC connection to the
337   *          database.
338   * @param fetchedDestEntry
339   *          the LDAP entry which corresponds to the database "entry" to modify
340   * @param modsToApply
341   *          a list of Modification objects which should be applied
342   * @param  operation
343   *          the sync operation for this change
344   * @throws SQLException
345   *           if there is an error modifying the entry
346   */
347  @ThreadSafety(level = ThreadSafetyLevel.METHOD_THREADSAFE)
348  public abstract void modifyEntry(final TransactionContext ctx,
349                                   final Entry fetchedDestEntry,
350                                   final List<Modification> modsToApply,
351                                   final SyncOperation operation)
352                                          throws SQLException;
353
354
355
356  /**
357   * Delete a full "entry" from the database, corresponding to the LDAP
358   * {@link Entry} that is passed in. This method may perform multiple deletes
359   * or updates if necessary to fully delete the entry from the database.
360   * <p>
361   * A {@link TransactionContext} is provided, which allows
362   * controlled access to the target database. The context will contain a fresh
363   * fresh connection (i.e. a new transaction), and the Data Sync Server
364   * will always commit or rollback the transaction automatically, depending on
365   * whether this method returns normally or throws an exception. See the class
366   * level documentation for warnings and additional details.
367   * <p>
368   * This method <b>must be thread safe</b>, as it will be called repeatedly and
369   * concurrently by the Sync Pipe worker threads as they process DELETE
370   * operations.
371   *
372   * @param ctx
373   *          a TransactionContext which provides a valid JDBC connection to the
374   *          database.
375   * @param fetchedDestEntry
376   *          the LDAP entry which corresponds to the database "entry" to delete
377   * @param  operation
378   *          the sync operation for this change
379   * @throws SQLException
380   *           if there is an error deleting the entry
381   */
382  @ThreadSafety(level = ThreadSafetyLevel.METHOD_THREADSAFE)
383  public abstract void deleteEntry(final TransactionContext ctx,
384                                   final Entry fetchedDestEntry,
385                                   final SyncOperation operation)
386                                        throws SQLException;
387}