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-2013 UnboundID Corp.
026 */
027 package com.unboundid.directory.sdk.sync.scripting;
028
029 import java.sql.SQLException;
030 import java.util.List;
031
032 import com.unboundid.directory.sdk.common.internal.Configurable;
033 import com.unboundid.directory.sdk.sync.config.JDBCSyncDestinationConfig;
034 import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension;
035 import com.unboundid.directory.sdk.sync.types.SyncOperation;
036 import com.unboundid.directory.sdk.sync.types.SyncServerContext;
037 import com.unboundid.directory.sdk.sync.types.TransactionContext;
038 import com.unboundid.ldap.sdk.Entry;
039 import com.unboundid.ldap.sdk.Modification;
040 import com.unboundid.util.Extensible;
041 import com.unboundid.util.ThreadSafety;
042 import com.unboundid.util.ThreadSafetyLevel;
043 import com.unboundid.util.args.ArgumentException;
044 import 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)
107 public 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 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 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 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 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 }