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