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-2018 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 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 how the method returns. If a method call returns 079 * successfully, then the transaction will be committed. If an exception is 080 * thrown, then the transaction will be rolled back. In rare situations, it 081 * might be necessary for an implementation to perform its own commit or 082 * rollback of transactions by calling methods on {@link TransactionContext}. 083 * <p> 084 * Several of these methods throw {@link SQLException}, which should be used in 085 * the case of any database access error. For other types of errors, runtime 086 * exceptions may be used (IllegalStateException, NullPointerException, etc.). 087 * The Synchronization Server will automatically retry operations that fail, up 088 * to a configurable amount of attempts. The exception to this rule is if a 089 * SQLException is thrown with a SQL state string beginning with "08"; this 090 * indicates a connection error, and in this case the operation is retried 091 * indefinitely. For these reasons implementers should refrain from handling 092 * or wrapping any {@link SQLException} and instead let it be handled by the 093 * calling code. 094 * <BR> 095 * <H2>Configuring JDBC Sync Destinations</H2> 096 * In order to configure a JDBC sync destination based on this API and 097 * written in Java, use a command like: 098 * <PRE> 099 * dsconfig create-sync-destination \ 100 * --destination-name "<I>{destination-name}</I>" \ 101 * --type third-party-jdbc \ 102 * --set "server:{server-name}" \ 103 * --set "extension-class:<I>{class-name}</I>" \ 104 * --set "extension-argument:<I>{name=value}</I>" 105 * </PRE> 106 * where "<I>{destination-name}</I>" is the name to use for the JDBC sync 107 * destination instance, "<I>{server-name}</I>" is the name of the JDBC external 108 * server that will be used as the sync destination, "<I>{class-name}</I>" is 109 * the fully-qualified name of the Java class written using this API, and 110 * "<I>{name=value}</I>" represents name-value pairs for any arguments to 111 * provide to the JDBC sync destination. If multiple arguments should be 112 * provided to the JDBC sync destination, then the 113 * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be 114 * provided multiple times. 115 */ 116@Extensible() 117@SynchronizationServerExtension(appliesToLocalContent=false, 118 appliesToSynchronizedContent=true) 119public abstract class JDBCSyncDestination implements UnboundIDExtension, 120 Configurable, 121 ExampleUsageProvider 122{ 123 /** 124 * Creates a new instance of this JDBC Sync Destination. All sync 125 * destination implementations must include a default constructor, but any 126 * initialization should generally be done in the 127 * {@link #initializeJDBCSyncDestination} method. 128 */ 129 public JDBCSyncDestination() 130 { 131 //no implementation is required 132 } 133 134 135 136 /** 137 * {@inheritDoc} 138 */ 139 public abstract String getExtensionName(); 140 141 142 143 /** 144 * {@inheritDoc} 145 */ 146 public abstract String[] getExtensionDescription(); 147 148 149 150 /** 151 * {@inheritDoc} 152 */ 153 public Map<List<String>,String> getExamplesArgumentSets() 154 { 155 return Collections.emptyMap(); 156 } 157 158 159 160 /** 161 * {@inheritDoc} 162 */ 163 public void defineConfigArguments(final ArgumentParser parser) 164 throws ArgumentException 165 { 166 // No arguments will be allowed by default. 167 } 168 169 170 171 /** 172 * This hook is called when a Sync Pipe first starts up, or when the 173 * <i>resync</i> process first starts up. Any initialization of this sync 174 * destination should be performed here. This method should generally store 175 * the {@link SyncServerContext} in a class 176 * member so that it can be used elsewhere in the implementation. 177 * <p> 178 * A {@link TransactionContext} is provided, which allows 179 * controlled access to the target database. The context will contain a fresh 180 * fresh connection (i.e. a new transaction), and the Synchronization Server 181 * will always commit or rollback the transaction automatically, depending on 182 * whether this method returns normally or throws an exception. See the class 183 * level documentation for warnings and additional details. 184 * <p> 185 * The default implementation is empty. 186 * 187 * @param ctx 188 * a TransactionContext which provides a valid JDBC connection to the 189 * database. 190 * @param serverContext A handle to the server context for the server in 191 * which this extension is running. 192 * @param config The general configuration for this sync destination. 193 * @param parser The argument parser which has been initialized from 194 * the configuration for this JDBC sync destination. 195 */ 196 @ThreadSafety(level = ThreadSafetyLevel.METHOD_NOT_THREADSAFE) 197 public void initializeJDBCSyncDestination( 198 final TransactionContext ctx, 199 final SyncServerContext serverContext, 200 final JDBCSyncDestinationConfig config, 201 final ArgumentParser parser) 202 { 203 // No initialization will be performed by default. 204 } 205 206 207 208 /** 209 * This hook is called when a Sync Pipe shuts down, or when the <i>resync</i> 210 * process shuts down. Any clean-up of this sync destination should be 211 * performed here. 212 * <p> 213 * A {@link TransactionContext} is provided, which allows 214 * controlled access to the target database. The context will contain a fresh 215 * fresh connection (i.e. a new transaction), and the Synchronization Server 216 * will always commit or rollback the transaction automatically, depending on 217 * whether this method returns normally or throws an exception. See the class 218 * level documentation for warnings and additional details. 219 * <p> 220 * The default implementation is empty. 221 * 222 * @param ctx 223 * a TransactionContext which provides a valid JDBC connection to the 224 * database. 225 */ 226 @ThreadSafety(level = ThreadSafetyLevel.METHOD_NOT_THREADSAFE) 227 public void finalizeJDBCSyncDestination( 228 final TransactionContext ctx) 229 { 230 //No implementation required by default. 231 } 232 233 234 235 /** 236 * Return a full destination entry (in LDAP form) from the database, 237 * corresponding to the source {@link Entry} that is passed in. 238 * This method should perform any queries necessary to gather the latest 239 * values for all the attributes to be synchronized and return them in an 240 * Entry. 241 * <p> 242 * Note that the if the source entry was renamed (see 243 * {@link SyncOperation#isModifyDN}), the 244 * <code>destEntryMappedFromSrc</code> will have the new DN; the old DN can 245 * be obtained by calling 246 * {@link SyncOperation#getDestinationEntryBeforeChange()} and getting the DN 247 * from there. This method should return the entry in its existing form 248 * (i.e. with the old DN, before it is changed). 249 * <p> 250 * A {@link TransactionContext} is provided, which allows 251 * controlled access to the target database. The context will contain a fresh 252 * fresh connection (i.e. a new transaction), and the Synchronization Server 253 * will always commit or rollback the transaction automatically, depending on 254 * whether this method returns normally or throws an exception. See the class 255 * level documentation for warnings and additional details. 256 * <p> 257 * This method <b>must be thread safe</b>, as it will be called repeatedly and 258 * concurrently by each of the Sync Pipe worker threads as they process 259 * entries. 260 * 261 * @param ctx 262 * a TransactionContext which provides a valid JDBC connection to the 263 * database. 264 * @param destEntryMappedFromSrc 265 * the LDAP entry which corresponds to the database "entry" to fetch 266 * @param operation 267 * the sync operation for this change 268 * @return a full LDAP Entry, or null if no such entry exists. 269 * @throws SQLException 270 * if there is an error fetching the entry 271 */ 272 @ThreadSafety(level = ThreadSafetyLevel.METHOD_THREADSAFE) 273 public abstract Entry fetchEntry(final TransactionContext ctx, 274 final Entry destEntryMappedFromSrc, 275 final SyncOperation operation) 276 throws SQLException; 277 278 279 280 /** 281 * Creates a full database "entry", corresponding to the LDAP 282 * {@link Entry} that is passed in. This method should perform any inserts and 283 * updates necessary to make sure the entry is fully created on the database. 284 * <p> 285 * A {@link TransactionContext} is provided, which allows 286 * controlled access to the target database. The context will contain a fresh 287 * fresh connection (i.e. a new transaction), and the Synchronization Server 288 * will always commit or rollback the transaction automatically, depending on 289 * whether this method returns normally or throws an exception. See the class 290 * level documentation for warnings and additional details. 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 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. See the class 331 * level documentation for warnings and additional details. 332 * <p> 333 * This method <b>must be thread safe</b>, as it will be called repeatedly and 334 * concurrently by the Sync Pipe worker threads as they process MODIFY 335 * operations. 336 * @param ctx 337 * a TransactionContext which provides a valid JDBC connection to the 338 * database. 339 * @param fetchedDestEntry 340 * the LDAP entry which corresponds to the database "entry" to modify 341 * @param modsToApply 342 * a list of Modification objects which should be applied 343 * @param operation 344 * the sync operation for this change 345 * @throws SQLException 346 * if there is an error modifying the entry 347 */ 348 @ThreadSafety(level = ThreadSafetyLevel.METHOD_THREADSAFE) 349 public abstract void modifyEntry(final TransactionContext ctx, 350 final Entry fetchedDestEntry, 351 final List<Modification> modsToApply, 352 final SyncOperation operation) 353 throws SQLException; 354 355 356 357 /** 358 * Delete a full "entry" from the database, corresponding to the LDAP 359 * {@link Entry} that is passed in. This method may perform multiple deletes 360 * or updates if necessary to fully delete the entry from the database. 361 * <p> 362 * A {@link TransactionContext} is provided, which allows 363 * controlled access to the target database. The context will contain a fresh 364 * fresh connection (i.e. a new transaction), and the Synchronization Server 365 * will always commit or rollback the transaction automatically, depending on 366 * whether this method returns normally or throws an exception. See the class 367 * level documentation for warnings and additional details. 368 * <p> 369 * This method <b>must be thread safe</b>, as it will be called repeatedly and 370 * concurrently by the Sync Pipe worker threads as they process DELETE 371 * operations. 372 * 373 * @param ctx 374 * a TransactionContext which provides a valid JDBC connection to the 375 * database. 376 * @param fetchedDestEntry 377 * the LDAP entry which corresponds to the database "entry" to delete 378 * @param operation 379 * the sync operation for this change 380 * @throws SQLException 381 * if there is an error deleting the entry 382 */ 383 @ThreadSafety(level = ThreadSafetyLevel.METHOD_THREADSAFE) 384 public abstract void deleteEntry(final TransactionContext ctx, 385 final Entry fetchedDestEntry, 386 final SyncOperation operation) 387 throws SQLException; 388}