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 2018-2024 Ping Identity Corporation 026 */ 027package com.unboundid.directory.sdk.sync.api; 028 029import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider; 030import com.unboundid.directory.sdk.common.internal.Reconfigurable; 031import com.unboundid.directory.sdk.common.internal.UnboundIDExtension; 032import com.unboundid.directory.sdk.sync.config.ChangeDetectorConfig; 033import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension; 034import com.unboundid.directory.sdk.sync.types.ChangeRecord; 035import com.unboundid.directory.sdk.sync.types.EndpointException; 036import com.unboundid.directory.sdk.sync.types.SetStartpointOptions; 037import com.unboundid.directory.sdk.sync.types.SyncOperation; 038import com.unboundid.directory.sdk.sync.types.SyncServerContext; 039import com.unboundid.directory.sdk.sync.types.SyncSourceContext; 040import com.unboundid.ldap.sdk.ResultCode; 041import com.unboundid.util.Extensible; 042import com.unboundid.util.args.ArgumentException; 043import com.unboundid.util.args.ArgumentParser; 044 045import java.io.Serializable; 046import java.util.Collections; 047import java.util.LinkedList; 048import java.util.List; 049import java.util.Map; 050import java.util.concurrent.atomic.AtomicLong; 051 052/** 053 * This class defines an API that must be implemented by extensions that 054 * detect changes for an LDAP based Sync Source. 055 * A Change Detector can be used to 056 * <ul> 057 * <li>Process logs or other flat files for changes.</li> 058 * <li>Process changes from a Queue (Kafka, RabbitMQ, etc)</li> 059 * <li>Override the standard cn=changelog 060 * based approach for detecting changes.</li> 061 * </ul> 062 * <br> 063 * <h2>Configuring Change Detectors</h2> 064 * In order to configure a Change Detector created using this API, use a 065 * command like: 066 * <pre> 067 * dsconfig create-change-detector \ 068 * --detector-name "<i>{detector-name}</i>" \ 069 * --type third-party \ 070 * --set "extension-class:<i>{class-name}</i>" \ 071 * --set "extension-argument:<i>{name=vale}</i>" 072 * </pre> 073 * where "<i>{plugin-name}</i>" is the name to use for the Change Detector 074 * instance, "<i>{class-name}</i>" is the fully-qualified name of the Java 075 * class that extends 076 * {@code com.unboundid.directory.sdk.sync.api.ChangeDetector}, 077 * and "<i>{name=value}</i>" represents name-value pairs for any arguments to 078 * provide to the Change Detector. If multiple arguments should be provided 079 * to the Change Detector, then the 080 * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" 081 * option should be provided multiple times. 082 */ 083@Extensible() 084@SynchronizationServerExtension(appliesToLocalContent = false, 085 appliesToSynchronizedContent = true) 086public abstract class ChangeDetector 087 implements UnboundIDExtension, 088 Reconfigurable<ChangeDetectorConfig>, 089 ExampleUsageProvider { 090 091 /** 092 * Creates a new instance of this LDAP Change Detector. All Change 093 * Detector implementations must include a default constructor, but any 094 * initialization should generally be done in the 095 * {@code initializeChangeDetector} method. 096 */ 097 public ChangeDetector() { 098 // No implementation is required. 099 } 100 101 /** 102 * {@inheritDoc} 103 */ 104 @Override 105 public abstract String getExtensionName(); 106 107 /** 108 * {@inheritDoc} 109 */ 110 @Override 111 public abstract String[] getExtensionDescription(); 112 113 /** 114 * {@inheritDoc} 115 */ 116 @Override 117 public Map<List<String>, String> getExamplesArgumentSets() { 118 return Collections.emptyMap(); 119 } 120 121 /** 122 * {@inheritDoc} 123 */ 124 @Override 125 public void defineConfigArguments(final ArgumentParser parser) 126 throws ArgumentException { 127 // No arguments will be allowed by default. 128 } 129 130 /** 131 * This hook is called when a Sync Pipe first starts up or when the 132 * set-startpoint subcommand is called from the <i>realtime-sync</i> command 133 * line tool. Any initialization of this change detector should be performed 134 * here. This method should generally store the {@link SyncServerContext} 135 * and {@link SyncSourceContext} in a class member so that it can be used 136 * elsewhere in the implementation. 137 * <p> 138 * The default implementation is empty. 139 * 140 * @param serverContext A handle to the server context for the server in 141 * which this extension is running. 142 * @param syncSourceContext An interface for interacting with the Sync 143 * Source that owns this Change Detector or 144 * {@code null} if the Change Detector is only 145 * being initialized to validate its configuration. 146 * @param parser The argument parser which has been initialized 147 * from the configuration for this sync source. 148 */ 149 public void initializeChangeDetector( 150 final SyncServerContext serverContext, 151 final SyncSourceContext syncSourceContext, 152 final ArgumentParser parser) { 153 // No initialization will be performed by default. 154 } 155 156 /** 157 * This hook is called when a Sync Pipe shuts down or when the set-startpoint 158 * subcommand (from the <i>realtime-sync</i> command line tool) is finished. 159 * Any clean up of this change detector should be performed here. 160 * <p> 161 * The default implementation is empty. 162 */ 163 public void finalizeChangeDetector() { 164 //No implementation required by default. 165 } 166 167 /** 168 * {@inheritDoc} 169 */ 170 public boolean isConfigurationAcceptable( 171 final ChangeDetectorConfig config, 172 final ArgumentParser parser, 173 final List<String> unacceptableReasons) { 174 // No implementation required by default. 175 return true; 176 } 177 178 /** 179 * {@inheritDoc} 180 */ 181 public ResultCode applyConfiguration(final ChangeDetectorConfig config, 182 final ArgumentParser parser, 183 final List<String> adminActionsRequired, 184 final List<String> messages) { 185 // No implementation required by default. 186 return ResultCode.SUCCESS; 187 } 188 189 /** 190 * This method should effectively set the starting point for synchronization 191 * to the place specified by the <code>options</code> parameter. This should 192 * cause all changes previous to the specified start point to be disregarded 193 * and only changes after that point to be returned by 194 * {@link #getNextBatchOfChanges(int, AtomicLong)}. 195 * <p> 196 * There are several different startpoint types (see 197 * {@link SetStartpointOptions}), and this implementation is not required to 198 * support them all. If the specified startpoint type is unsupported, this 199 * method should throw an {@link UnsupportedOperationException}. 200 * 201 * <b>IMPORTANT</b>: The <code>RESUME_AT_SERIALIZABLE</code> startpoint type 202 * must be supported by your implementation, because this is used when a Sync 203 * Pipe first starts up. The {@link Serializable} in this case is the same 204 * type that is returned by {@link #getStartpoint()}; the Sync Server persists 205 * it and passes it back in on a restart. 206 * <p> 207 * This method can be called from two different contexts: 208 * <ul> 209 * <li>When the 'set-startpoint' subcommand of the realtime-sync CLI is used 210 * (the Sync Pipe is required to be stopped in this context)</li> 211 * <li>Immediately after a Sync Pipe starts up and a connection is first 212 * established to the source server (e.g. before the first call to 213 * {@link #getNextBatchOfChanges(int, AtomicLong)})</li> 214 * </ul> 215 * 216 * @param options an object which indicates where exactly to start 217 * synchronizing (e.g. the end of the changelog, specific 218 * change number, a certain time ago, etc) 219 * @throws EndpointException if there is any error while setting the 220 * start point 221 */ 222 public abstract void setStartpoint(final SetStartpointOptions options) 223 throws EndpointException; 224 225 226 /** 227 * Gets the current value of the startpoint for change detection. This is the 228 * "bookmark" which indicates which changes have already been processed and 229 * which have not. In most cases, a change number is used to detect changes 230 * and is managed by the Data Sync Server, in which case this 231 * implementation needs only to return the latest acknowledged 232 * change number. In other cases, the return value may correspond to a 233 * different value, such as the SYS_CHANGE_VERSION in Microsoft SQL Server. 234 * In any case, this method should return the value that is updated by 235 * {@link #acknowledgeCompletedOps(LinkedList)}. 236 * <p> 237 * This method is called periodically and the return value is saved in the 238 * persistent state for the Sync Pipe that uses this extension as its Sync 239 * Source. 240 * 241 * <b>IMPORTANT</b>: The internal value for the startpoint should only be 242 * updated after a sync operation is acknowledged back to this extension (via 243 * {@link #acknowledgeCompletedOps(LinkedList)}). 244 * Otherwise it will be possible for changes to be missed when the 245 * Data Sync Server is restarted or a connection error occurs. 246 * 247 * @return a value to store in the persistent state for the Sync Pipe. This is 248 * usually a change number, but if a changelog table is not used to 249 * detect changes, this value should represent some other token to 250 * pass into {@link #setStartpoint(SetStartpointOptions)} 251 * when the sync pipe starts up. 252 */ 253 public abstract Serializable getStartpoint(); 254 255 /** 256 * Return the next batch of change records from the source. Change records 257 * are usually just hints that a change happened; they do not include 258 * the full contents of the target entry. In an effort to never synchronize 259 * stale data, the Data Sync Server will go back and fetch the full 260 * target entry for each change record. 261 * <p> 262 * On the first invocation, this should return changes starting from the 263 * startpoint that was set by 264 * {@link #setStartpoint(SetStartpointOptions)}. This method is also 265 * responsible for updating the internal state such that subsequent 266 * invocations do not return duplicate changes. 267 * <p> 268 * The resulting list should be limited by <code>maxChanges</code>. The 269 * <code>numStillPending</code> reference should be set to the estimated 270 * number of changes that haven't yet been retrieved from the source endpoint 271 * when this method returns, or zero if all the current changes have been 272 * retrieved. 273 * 274 * <b>IMPORTANT</b>: While this method needs to keep track of which changes 275 * have already been returned so that it does not return them again, it should 276 * <b>NOT</b> modify the official startpoint. The internal value for the 277 * startpoint should only be updated after a sync operation is acknowledged 278 * back to this extension (via 279 * {@link #acknowledgeCompletedOps(LinkedList)}). 280 * Otherwise it will be possible for changes to be missed when the 281 * Data Sync Server is restarted or a connection error occurs. The 282 * startpoint should not change as a result of this method. 283 * <p> 284 * This method <b>does not need to be thread-safe</b>. It will be invoked 285 * repeatedly by a single thread, based on the polling interval set in the 286 * Sync Pipe configuration. 287 * 288 * @param maxChanges the maximum number of changes to retrieve 289 * @param numStillPending this should be set to the number of unretrieved 290 * changes that are still pending after this batch has 291 * been retrieved. This will be passed in as zero, and 292 * may be left that way if the actual value cannot be 293 * determined. 294 * @return a list of {@link ChangeRecord} instances, each 295 * corresponding to a single change at the source endpoint. 296 * If there are no new changes to return, this method should return 297 * an empty list. 298 * @throws EndpointException if there is any error while retrieving the 299 * next batch of changes 300 */ 301 public abstract List<ChangeRecord> getNextBatchOfChanges( 302 final int maxChanges, 303 final AtomicLong numStillPending) 304 throws EndpointException; 305 306 /** 307 * Provides a way for the Data Sync Server to acknowledge back to the 308 * extension which sync operations it has processed. This method should update 309 * the official startpoint which was set by 310 * {@link #setStartpoint(SetStartpointOptions)} and is 311 * returned by {@link #getStartpoint()}. 312 * 313 * <b>IMPORTANT</b>: The internal value for the startpoint should only be 314 * updated after a sync operation is acknowledged back to this extension (via 315 * this method). Otherwise it will be possible for changes to be missed when 316 * the Data Sync Server is restarted or a connection error occurs. 317 * 318 * @param completedOps a list of {@link SyncOperation}s that have finished 319 * processing. The records are listed in the order they 320 * were first detected. 321 * @throws EndpointException if there is an error acknowledging the changes 322 * back to the source 323 */ 324 public abstract void acknowledgeCompletedOps( 325 final LinkedList<SyncOperation> completedOps) 326 throws EndpointException; 327}