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-2014 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 }