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.scripting; 028 029 030 031import java.util.List; 032 033import com.unboundid.directory.sdk.common.internal.Reconfigurable; 034import com.unboundid.directory.sdk.sync.config.LDAPSyncDestinationPluginConfig; 035import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension; 036import com.unboundid.directory.sdk.sync.types.PostStepResult; 037import com.unboundid.directory.sdk.sync.types.PreStepResult; 038import com.unboundid.directory.sdk.sync.types.SyncOperation; 039import com.unboundid.directory.sdk.sync.types.SyncServerContext; 040import com.unboundid.ldap.sdk.Entry; 041import com.unboundid.ldap.sdk.LDAPException; 042import com.unboundid.ldap.sdk.LDAPInterface; 043import com.unboundid.ldap.sdk.Modification; 044import com.unboundid.ldap.sdk.ResultCode; 045import com.unboundid.ldap.sdk.SearchRequest; 046import com.unboundid.ldap.sdk.UpdatableLDAPRequest; 047import com.unboundid.util.Extensible; 048import com.unboundid.util.ThreadSafety; 049import com.unboundid.util.ThreadSafetyLevel; 050import com.unboundid.util.args.ArgumentException; 051import com.unboundid.util.args.ArgumentParser; 052 053 054 055/** 056 * This class defines an API that must be implemented by scripted extensions 057 * that perform processing on synchronization operations within an LDAP Sync 058 * Destination. These extensions may be used to 059 * <ul> 060 * <li>Filter out certain changes from being synchronized.</li> 061 * <li>Change how an entry is fetched.</li> 062 * <li>Change how an entry is modified or created.</li> 063 * </ul> 064 * <BR> 065 * A note on exception handling: in general subclasses should not 066 * catch LDAPExceptions that are thrown when using the provided 067 * LDAPInterface unless there are specific exceptions that are 068 * expected. The Data Sync Server will handle 069 * LDAPExceptions in an appropriate way based on the specific 070 * cause of the exception. For example, some errors will result 071 * in the SyncOperation being retried, and others will trigger 072 * fail over to a different server. 073 * <BR> 074 * <H2>Configuring Groovy-Scripted LDAP Sync Destination Plugins</H2> 075 * In order to configure a scripted LDAP sync destination plugin based on this 076 * API and written in the Groovy scripting language, use a command like: 077 * <PRE> 078 * dsconfig create-sync-destination-plugin \ 079 * --plugin-name "<I>{plugin-name}</I>" \ 080 * --type groovy-scripted-ldap \ 081 * --set "script-class:<I>{class-name}</I>" \ 082 * --set "script-argument:<I>{name=value}</I>" 083 * </PRE> 084 * where "<I>{plugin-name}</I>" is the name to use for the LDAP sync destination 085 * plugin instance, "<I>{class-name}</I>" is the fully-qualified name of the 086 * Groovy class written using this API, and "<I>{name=value}</I>" represents 087 * name-value pairs for any arguments to provide to the LDAP sync destination 088 * plugin. If multiple arguments should be provided to the LDAP sync 089 * destination plugin, then the 090 * "<CODE>--set script-argument:<I>{name=value}</I></CODE>" option should be 091 * provided multiple times. 092 * 093 * @see com.unboundid.directory.sdk.sync.api.LDAPSyncDestinationPlugin 094 */ 095@Extensible() 096@SynchronizationServerExtension(appliesToLocalContent=false, 097 appliesToSynchronizedContent=true) 098@ThreadSafety(level= ThreadSafetyLevel.INTERFACE_THREADSAFE) 099public abstract class ScriptedLDAPSyncDestinationPlugin 100 implements Reconfigurable<LDAPSyncDestinationPluginConfig> 101{ 102 /** 103 * Creates a new instance of this LDAP sync destination plugin. All sync 104 * destination implementations must include a default constructor, but any 105 * initialization should generally be done in the 106 * {@code initializeLDAPSyncDestinationPlugin} method. 107 */ 108 public ScriptedLDAPSyncDestinationPlugin() 109 { 110 // No implementation is required. 111 } 112 113 114 115 /** 116 * {@inheritDoc} 117 */ 118 public void defineConfigArguments(final ArgumentParser parser) 119 throws ArgumentException 120 { 121 // No arguments will be allowed by default. 122 } 123 124 125 126 /** 127 * Initializes this LDAP sync destination plugin. 128 * 129 * @param serverContext A handle to the server context for the server in 130 * which this extension is running. 131 * @param config The general configuration for this LDAP sync 132 * destination plugin transformation. 133 * @param parser The argument parser which has been initialized from 134 * the configuration for this LDAP sync destination 135 * plugin. 136 * 137 * @throws LDAPException If a problem occurs while initializing this LDAP 138 * sync destination plugin. 139 */ 140 public void initializeLDAPSyncDestinationPlugin( 141 final SyncServerContext serverContext, 142 final LDAPSyncDestinationPluginConfig config, 143 final ArgumentParser parser) 144 throws LDAPException 145 { 146 // No initialization will be performed by default. 147 } 148 149 150 151 /** 152 * Performs any cleanup which may be necessary when this LDAP sync destination 153 * plugin is to be taken out of service. 154 */ 155 public void finalizeLDAPSyncDestinationPlugin() 156 { 157 // No implementation is required. 158 } 159 160 161 162 /** 163 * {@inheritDoc} 164 */ 165 public boolean isConfigurationAcceptable( 166 final LDAPSyncDestinationPluginConfig config, 167 final ArgumentParser parser, 168 final List<String> unacceptableReasons) 169 { 170 // No extended validation will be performed. 171 return true; 172 } 173 174 175 176 /** 177 * {@inheritDoc} 178 */ 179 public ResultCode applyConfiguration( 180 final LDAPSyncDestinationPluginConfig config, 181 final ArgumentParser parser, 182 final List<String> adminActionsRequired, 183 final List<String> messages) 184 { 185 // By default, no configuration changes will be applied. 186 return ResultCode.SUCCESS; 187 } 188 189 190 191 /** 192 * This method is called before a destination entry is fetched. A 193 * connection to the destination server is provided along with the 194 * {@code SearchRequest} that will be sent to the server. This method is 195 * overridden by plugins that need to have access to the search request 196 * before it is sent to the destination server. This includes updating the 197 * search request as well as performing the search instead of the core server, 198 * including doing additional searches. For plugins that need to manipulate 199 * the entries that the core LDAP Sync Destination code retrieves from the 200 * destination, implementing the {@link #postFetch} method is more natural. 201 * <p> 202 * This method might be called multiple times for a single synchronization 203 * operation, specifically when there are multiple search criteria or 204 * multiple base DNs defined for the Sync Destination. 205 * 206 * @param destinationConnection A connection to the destination server. 207 * @param searchRequest The search request that the LDAP Sync 208 * Destination will use to fetch the entry. 209 * @param fetchedEntries A list of entries that have been fetched. 210 * When the search criteria matches multiple 211 * entries, they should all be returned. A 212 * plugin that wishes to implement the fetch 213 * should put the fetched entries here and 214 * return 215 * {@code PreStepResult#SKIP_CURRENT_STEP}. 216 * @param operation The synchronization operation for this 217 * change. 218 * 219 * @return The result of the plugin processing. Note: 220 * {@code PreStepResult#SKIP_CURRENT_STEP} should only be returned 221 * if this plugin takes responsibility for fully fetching the entry 222 * according to the search request and for populating the 223 * fetched entry list. 224 * 225 * @throws LDAPException In general subclasses should not catch 226 * LDAPExceptions that are thrown when 227 * using the LDAPInterface unless there 228 * are specific exceptions that are 229 * expected. The Data Sync Server 230 * will handle LDAPExceptions in an 231 * appropriate way based on the specific 232 * cause of the exception. For example, 233 * some errors will result in the 234 * SyncOperation being retried, and others 235 * will trigger fail over to a different 236 * server. Plugins should only throw 237 * LDAPException for errors related to 238 * communication with the LDAP server. 239 * Use the return code to indicate other 240 * types of errors, which might require 241 * retry. 242 */ 243 public PreStepResult preFetch(final LDAPInterface destinationConnection, 244 final SearchRequest searchRequest, 245 final List<Entry> fetchedEntries, 246 final SyncOperation operation) 247 throws LDAPException 248 { 249 return PreStepResult.CONTINUE; 250 } 251 252 253 254 /** 255 * This method is called after an attempt to fetch a destination entry. An 256 * connection to the destination server is provided along with the 257 * {@code SearchRequest} that was sent to the server. This method is 258 * overridden by plugins that need to manipulate the search results that 259 * are returned to the Sync Pipe. This can include filtering out certain 260 * entries, remove information from the entries, or adding additional 261 * information, possibly by doing a followup LDAP search. 262 * <p> 263 * This method might be called multiple times for a single synchronization 264 * operation, specifically when there are multiple search criteria or 265 * multiple base DNs defined for the Sync Destination. 266 * <p> 267 * This method will not be called if the search fails, for instance, if 268 * the base DN of the search does not exist. 269 * 270 * @param destinationConnection A connection to the destination server. 271 * @param searchRequest The search request that the LDAP Sync 272 * Destination used to fetch the entry. 273 * @param fetchedEntries A list of entries that have been fetched. 274 * When the search criteria matches multiple 275 * entries, they will all be returned. Entries 276 * in this list can be edited directly, and the 277 * list can be edited as well. 278 * @param operation The synchronization operation for this 279 * change. 280 * 281 * @return The result of the plugin processing. 282 * 283 * @throws LDAPException In general subclasses should not catch 284 * LDAPExceptions that are thrown when 285 * using the LDAPInterface unless there 286 * are specific exceptions that are 287 * expected. The Data Sync Server 288 * will handle LDAPExceptions in an 289 * appropriate way based on the specific 290 * cause of the exception. For example, 291 * some errors will result in the 292 * SyncOperation being retried, and others 293 * will trigger fail over to a different 294 * server. Plugins should only throw 295 * LDAPException for errors related to 296 * communication with the LDAP server. 297 * Use the return code to indicate other 298 * types of errors, which might require 299 * retry. 300 */ 301 public PostStepResult postFetch(final LDAPInterface destinationConnection, 302 final SearchRequest searchRequest, 303 final List<Entry> fetchedEntries, 304 final SyncOperation operation) 305 throws LDAPException 306 { 307 return PostStepResult.CONTINUE; 308 } 309 310 311 312 /** 313 * This method is called before a destination entry is created. A 314 * connection to the destination server is provided along with the 315 * {@code Entry} that will be sent to the server. This method is 316 * overridden by plugins that need to alter the entry before it is created 317 * at the server. 318 * 319 * @param destinationConnection A connection to the destination server. 320 * @param entryToCreate The entry that will be created at the 321 * destination. A plugin that wishes to 322 * create the entry should be sure to return 323 * {@code PreStepResult#SKIP_CURRENT_STEP}. 324 * @param operation The synchronization operation for this 325 * change. 326 * 327 * @return The result of the plugin processing. 328 * 329 * @throws LDAPException In general subclasses should not catch 330 * LDAPExceptions that are thrown when 331 * using the LDAPInterface unless there 332 * are specific exceptions that are 333 * expected. The Data Sync Server 334 * will handle LDAPExceptions in an 335 * appropriate way based on the specific 336 * cause of the exception. For example, 337 * some errors will result in the 338 * SyncOperation being retried, and others 339 * will trigger fail over to a different 340 * server. Plugins should only throw 341 * LDAPException for errors related to 342 * communication with the LDAP server. 343 * Use the return code to indicate other 344 * types of errors, which might require 345 * retry. 346 */ 347 public PreStepResult preCreate(final LDAPInterface destinationConnection, 348 final Entry entryToCreate, 349 final SyncOperation operation) 350 throws LDAPException 351 { 352 return PreStepResult.CONTINUE; 353 } 354 355 356 357 /** 358 * This method is called before a destination entry is modified. A 359 * connection to the destination server is provided along with the 360 * {@code Entry} that will be sent to the server. This method is 361 * overridden by plugins that need to perform some processing on an entry 362 * before it is modified. 363 * 364 * @param destinationConnection A connection to the destination server. 365 * @param entryToModify The entry that will be modified at the 366 * destination. A plugin that wishes to 367 * modify the entry should be sure to return 368 * {@code PreStepResult#SKIP_CURRENT_STEP}. 369 * @param modsToApply A modifiable list of the modifications to 370 * apply at the server. 371 * @param operation The synchronization operation for this 372 * change. 373 * 374 * @return The result of the plugin processing. 375 * 376 * @throws LDAPException In general subclasses should not catch 377 * LDAPExceptions that are thrown when 378 * using the LDAPInterface unless there 379 * are specific exceptions that are 380 * expected. The Data Sync Server 381 * will handle LDAPExceptions in an 382 * appropriate way based on the specific 383 * cause of the exception. For example, 384 * some errors will result in the 385 * SyncOperation being retried, and others 386 * will trigger fail over to a different 387 * server. Plugins should only throw 388 * LDAPException for errors related to 389 * communication with the LDAP server. 390 * Use the return code to indicate other 391 * types of errors, which might require 392 * retry. 393 */ 394 public PreStepResult preModify(final LDAPInterface destinationConnection, 395 final Entry entryToModify, 396 final List<Modification> modsToApply, 397 final SyncOperation operation) 398 throws LDAPException 399 { 400 return PreStepResult.CONTINUE; 401 } 402 403 404 405 /** 406 * This method is called before a destination entry is deleted. A 407 * connection to the destination server is provided along with the 408 * {@code Entry} that will be sent to the server. This method is 409 * overridden by plugins that need to perform some processing on an entry 410 * before it is deleted. A plugin could choose to mark an entry as disabled 411 * instead of deleting it for instance, or move the entry to a different 412 * part of the directory hierarchy. 413 * 414 * @param destinationConnection A connection to the destination server. 415 * @param entryToDelete The entry that will be deleted at the 416 * destination. A plugin that wishes to 417 * delete the entry should be sure to return 418 * {@code PreStepResult#SKIP_CURRENT_STEP}. 419 * @param operation The synchronization operation for this 420 * change. 421 * 422 * @return The result of the plugin processing. 423 * 424 * @throws LDAPException In general subclasses should not catch 425 * LDAPExceptions that are thrown when 426 * using the LDAPInterface unless there 427 * are specific exceptions that are 428 * expected. The Data Sync Server 429 * will handle LDAPExceptions in an 430 * appropriate way based on the specific 431 * cause of the exception. For example, 432 * some errors will result in the 433 * SyncOperation being retried, and others 434 * will trigger fail over to a different 435 * server. Plugins should only throw 436 * LDAPException for errors related to 437 * communication with the LDAP server. 438 * Use the return code to indicate other 439 * types of errors, which might require 440 * retry. 441 */ 442 public PreStepResult preDelete(final LDAPInterface destinationConnection, 443 final Entry entryToDelete, 444 final SyncOperation operation) 445 throws LDAPException 446 { 447 return PreStepResult.CONTINUE; 448 } 449 450 451 452 /** 453 * This method is called prior to executing any add, modify, delete, or 454 * search from the destination but after the respective pre method (e.g 455 * preFetch or preModify). A connection to the destination server is provided 456 * along with the {@code UpdatableLDAPRequest} that will be sent to the 457 * server. this method is overridden by plugins that need to modify the 458 * LDAP request prior to execution. For example, attaching a {@code Control} 459 * to the request. Callers of this method can use {@code instanceof} 460 * to determine which type of LDAP request is being made. 461 * 462 * @param destinationConnection A connection to the destination server. 463 * @param request The LDAP request that will be sent to 464 * the destination server. 465 * @param operation The synchronization operation for this 466 * change. 467 * 468 * @return The result of the plugin processing. Be very careful when 469 * returning {@code PreStepResult#RETRY_OPERATION_UNLIMITED} as this 470 * can stall all in flight operations until this operation completes. 471 * This return value should only be used in situations where a 472 * remote service (e.g., the LDAP server) is unavailable. In this 473 * case, it's preferable to just throw the underlying LDAPException, 474 * which the Data Sync Server will handle correctly based on 475 * the type of the operation. 476 * 477 * @throws LDAPException In general subclasses should not catch 478 * LDAPExceptions that are thrown when 479 * using the LDAPInterface unless there 480 * are specific exceptions that are 481 * expected. The Data Sync Server 482 * will handle LDAPExceptions in an 483 * appropriate way based on the specific 484 * cause of the exception. For example, 485 * some errors will result in the 486 * SyncOperation being retried, and others 487 * will trigger fail over to a different 488 * server. Plugins should only throw 489 * LDAPException for errors related to 490 * communication with the LDAP server. 491 * Use the return code to indicate other 492 * types of errors, which might require 493 * retry. 494 */ 495 public PreStepResult transformRequest( 496 final LDAPInterface destinationConnection, 497 final UpdatableLDAPRequest request, 498 final SyncOperation operation) 499 throws LDAPException 500 { 501 return PreStepResult.CONTINUE; 502 } 503}