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