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-2024 Ping Identity Corporation 026 */ 027package com.unboundid.directory.sdk.sync.scripting; 028 029import java.util.List; 030 031import com.unboundid.directory.sdk.common.internal.Configurable; 032import com.unboundid.directory.sdk.sync.config.SyncDestinationConfig; 033import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension; 034import com.unboundid.directory.sdk.sync.types.EndpointException; 035import com.unboundid.directory.sdk.sync.types.SyncOperation; 036import com.unboundid.directory.sdk.sync.types.SyncServerContext; 037import com.unboundid.ldap.sdk.Entry; 038import com.unboundid.ldap.sdk.Modification; 039import com.unboundid.util.Extensible; 040import com.unboundid.util.ThreadSafety; 041import com.unboundid.util.ThreadSafetyLevel; 042import com.unboundid.util.args.ArgumentException; 043import com.unboundid.util.args.ArgumentParser; 044 045/** 046 * This class defines an API that must be implemented by scripted extensions 047 * that wish to push changes processed by the Data Sync Server to an 048 * arbitrary destination. This type of sync destination is generic and can 049 * support a wide range of endpoints. In addition, this type of sync destination 050 * supports one-way notifications, where the source and destination entries are 051 * never compared but instead changes are pushed straight through. 052 * 053 * <H2>Configuring Scripted Sync Destinations</H2> 054 * In order to configure a sync destination created using this API, use 055 * a command like: 056 * <PRE> 057 * dsconfig create-sync-destination \ 058 * --sync-destination-name "<I>{name}</I>" \ 059 * --type groovy-scripted \ 060 * --set "script-class:<I>{class-name}</I>" \ 061 * --set "script-argument:<I>{name=value}</I>" 062 * </PRE> 063 * where "<I>{name}</I>" is the name to use for the sync destination 064 * instance, "<I>{script-name}</I>" is the fully-qualified name of the Java 065 * class that extends 066 * {@code com.unboundid.directory.sdk.sync.scripting.ScriptedSyncDestination}, 067 * and "<I>{name=value}</I>" represents name-value pairs for any arguments to 068 * provide to the sync destination. If multiple arguments should be 069 * provided to extension, then the 070 * "<CODE>--set script-argument:<I>{name=value}</I></CODE>" option should be 071 * provided multiple times. 072 * 073 * @see 074 * com.unboundid.directory.sdk.sync.api.SyncDestination 075 */ 076@Extensible() 077@SynchronizationServerExtension(appliesToLocalContent=false, 078 appliesToSynchronizedContent=true) 079@ThreadSafety(level = ThreadSafetyLevel.MOSTLY_THREADSAFE) 080public abstract class ScriptedSyncDestination implements Configurable 081{ 082 083 /** 084 * {@inheritDoc} 085 */ 086 public void defineConfigArguments(final ArgumentParser parser) 087 throws ArgumentException 088 { 089 // No arguments will be allowed by default. 090 } 091 092 093 094 /** 095 * Initializes this sync destination. This is called when a Sync Pipe 096 * first starts up, or when the <i>resync</i> process first starts up. Any 097 * initialization should be performed here. This method should generally store 098 * the {@link SyncServerContext} in a class member so that it can be used 099 * elsewhere in the implementation. 100 * <p> 101 * The default implementation is empty. 102 * 103 * @param serverContext A handle to the server context for the server in 104 * which this extension is running. Extensions should 105 * typically store this in a class member. 106 * @param config The general configuration for this object. 107 * @param parser The argument parser which has been initialized from 108 * the configuration for this sync destination. 109 * @throws EndpointException 110 * if a problem occurs while initializing this 111 * sync destination. 112 */ 113 public void initializeSyncDestination( 114 final SyncServerContext serverContext, 115 final SyncDestinationConfig config, 116 final ArgumentParser parser) 117 throws EndpointException 118 { 119 // No initialization will be performed by default. 120 } 121 122 123 124 /** 125 * This hook is called when a Sync Pipe shuts down, or when the <i>resync</i> 126 * process shuts down. Any clean-up of this sync destination should be 127 * performed here. 128 * <p> 129 * The default implementation is empty. 130 */ 131 public void finalizeSyncDestination() 132 { 133 // No implementation is performed by default. 134 } 135 136 137 138 /** 139 * Return the URL or path identifying the destination endpoint 140 * to which this extension is transmitting data. This is used for logging 141 * purposes only, so it could just be a server name or hostname and port, etc. 142 * 143 * @return the path to the destination endpoint 144 */ 145 public abstract String getCurrentEndpointURL(); 146 147 148 149 /** 150 * Return a full destination entry (in LDAP form) from the destination 151 * endpoint, corresponding to the source {@link Entry} that is passed in. 152 * This method should perform any queries necessary to gather the latest 153 * values for all the attributes to be synchronized and return them in an 154 * Entry. 155 * <p> 156 * This method only needs to be implemented if the 'synchronization-mode' on 157 * the Sync Pipe is set to 'standard'. If it is set to 'notification', this 158 * method will never be called, and the pipe will pass changes straight 159 * through to one of {@link #createEntry}, {@link #modifyEntry}, or 160 * {@link #deleteEntry}. 161 * <p> 162 * Note that the if the source entry was renamed (see 163 * {@link SyncOperation#isModifyDN}), the 164 * <code>destEntryMappedFromSrc</code> will have the new DN; the old DN can 165 * be obtained by calling 166 * {@link SyncOperation#getDestinationEntryBeforeChange()} and getting the DN 167 * from there. This method should return the entry in its existing form 168 * (i.e. with the old DN, before it is changed). 169 * <p> 170 * This method <b>must be thread safe</b>, as it will be called repeatedly and 171 * concurrently by each of the Sync Pipe worker threads as they process 172 * entries. 173 * @param destEntryMappedFromSrc 174 * the LDAP entry which corresponds to the destination "entry" to 175 * fetch 176 * @param operation 177 * the sync operation for this change 178 * @return a list containing the full LDAP entries that matched this search 179 * (there may be more than one), or an empty list if no such entry 180 * exists 181 * @throws EndpointException 182 * if there is an error fetching the entry 183 */ 184 public List<Entry> fetchEntry(final Entry destEntryMappedFromSrc, 185 final SyncOperation operation) 186 throws EndpointException 187 { 188 throw new UnsupportedOperationException( 189 "The fetchEntry() method must be implemented in the " + 190 "ScriptedSyncDestination if the Sync Pipe is " + 191 "running in standard mode (see 'synchronization-mode' " + 192 "in the Sync Pipe configuration)."); 193 } 194 195 196 197 /** 198 * Creates a full destination "entry", corresponding to the LDAP 199 * {@link Entry} that is passed in. This method is responsible for 200 * transforming the contents of the entry into the desired format and 201 * transmitting it to the target destination. It should perform any inserts or 202 * updates necessary to make sure the entry is fully created on the 203 * destination endpoint. 204 * <p> 205 * This method <b>must be thread safe</b>, as it will be called repeatedly and 206 * concurrently by the Sync Pipe worker threads as they process CREATE 207 * operations. 208 * @param entryToCreate 209 * the LDAP entry which corresponds to the destination 210 * "entry" to create 211 * @param operation 212 * the sync operation for this change 213 * @throws EndpointException 214 * if there is an error creating the entry 215 */ 216 public abstract void createEntry(final Entry entryToCreate, 217 final SyncOperation operation) 218 throws EndpointException; 219 220 221 222 /** 223 * Modify an "entry" on the destination, corresponding to the LDAP 224 * {@link Entry} that is passed in. This method is responsible for 225 * transforming the contents of the entry into the desired format and 226 * transmitting it to the target destination. It may perform multiple updates 227 * (including inserting or deleting other attributes) in order to fully 228 * synchronize the entire entry on the destination endpoint. 229 * <p> 230 * Note that the if the source entry was renamed (see 231 * {@link SyncOperation#isModifyDN}), the 232 * <code>fetchedDestEntry</code> will have the old DN; the new DN can 233 * be obtained by calling 234 * {@link SyncOperation#getDestinationEntryAfterChange()} and getting the DN 235 * from there. 236 * <p> 237 * This method <b>must be thread safe</b>, as it will be called repeatedly and 238 * concurrently by the Sync Pipe worker threads as they process MODIFY 239 * operations. 240 * @param entryToModify 241 * the LDAP entry which corresponds to the destination 242 * "entry" to modify. If the synchronization mode is 'standard', 243 * this will be the entry that was returned by {@link #fetchEntry}; 244 * otherwise if the synchronization mode is 'notification', this 245 * will be the mapped destination entry. 246 * @param modsToApply 247 * a list of Modification objects which should be applied 248 * @param operation 249 * the sync operation for this change 250 * @throws EndpointException 251 * if there is an error modifying the entry 252 */ 253 public abstract void modifyEntry(final Entry entryToModify, 254 final List<Modification> modsToApply, 255 final SyncOperation operation) 256 throws EndpointException; 257 258 259 260 /** 261 * Delete a full "entry" from the destination, corresponding to the LDAP 262 * {@link Entry} that is passed in. This method may perform multiple deletes 263 * or updates if necessary to fully delete the entry from the destination 264 * endpoint. 265 * <p> 266 * This method <b>must be thread safe</b>, as it will be called repeatedly and 267 * concurrently by the Sync Pipe worker threads as they process DELETE 268 * operations. 269 * @param entryToDelete 270 * the LDAP entry which corresponds to the destination 271 * "entry" to delete. If the synchronization mode is 'standard', 272 * this will be the entry that was returned by {@link #fetchEntry}; 273 * otherwise if the synchronization mode is 'notification', this 274 * will be the mapped destination entry. 275 * @param operation 276 * the sync operation for this change 277 * @throws EndpointException 278 * if there is an error deleting the entry 279 */ 280 public abstract void deleteEntry(final Entry entryToDelete, 281 final SyncOperation operation) 282 throws EndpointException; 283 284}