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