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