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