001/* 002 * Copyright 2010-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2010-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2010-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.controls; 037 038 039 040import java.util.ArrayList; 041 042import com.unboundid.asn1.ASN1Boolean; 043import com.unboundid.asn1.ASN1Constants; 044import com.unboundid.asn1.ASN1Element; 045import com.unboundid.asn1.ASN1Enumerated; 046import com.unboundid.asn1.ASN1OctetString; 047import com.unboundid.asn1.ASN1Sequence; 048import com.unboundid.ldap.sdk.Control; 049import com.unboundid.ldap.sdk.LDAPException; 050import com.unboundid.ldap.sdk.ResultCode; 051import com.unboundid.util.Debug; 052import com.unboundid.util.NotMutable; 053import com.unboundid.util.NotNull; 054import com.unboundid.util.Nullable; 055import com.unboundid.util.StaticUtils; 056import com.unboundid.util.ThreadSafety; 057import com.unboundid.util.ThreadSafetyLevel; 058import com.unboundid.util.Validator; 059 060import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 061 062 063 064/** 065 * This class provides an implementation of the LDAP content synchronization 066 * request control as defined in 067 * <a href="http://www.ietf.org/rfc/rfc4533.txt">RFC 4533</a>. It may be 068 * included in a search request to indicate that the client wishes to stay in 069 * sync with the server and/or be updated when server data changes. 070 * <BR><BR> 071 * Searches containing this control have the potential to take a very long time 072 * to complete (and may potentially never complete if the 073 * {@link ContentSyncRequestMode#REFRESH_AND_PERSIST} mode is selected), may 074 * return a large number of entries, and may also return intermediate response 075 * messages. When using this control, it is important to keep the following in 076 * mind: 077 * <UL> 078 * <LI>The associated search request should have a 079 * {@link com.unboundid.ldap.sdk.SearchResultListener} so that entries 080 * will be made available as soon as they are returned rather than having 081 * to wait for the search to complete and/or consuming a large amount of 082 * memory by storing the entries in a list that is only made available 083 * when the search completes. It may be desirable to use an 084 * {@link com.unboundid.ldap.sdk.AsyncSearchResultListener} to perform the 085 * search as an asynchronous operation so that the search request thread 086 * does not block while waiting for the search to complete.</LI> 087 * <LI>Entries and references returned from the search should include the 088 * {@link ContentSyncStateControl} with the associated entryUUID and 089 * potentially a cookie with an updated sync session state. You should 090 * call {@code getControl(ContentSyncStateControl.SYNC_STATE_OID)} on the 091 * search result entries and references in order to retrieve the control 092 * with the sync state information.</LI> 093 * <LI>The search request should be configured with an unlimited server-side 094 * time limit using {@code SearchRequest.setTimeLimitSeconds(0)}, and an 095 * unlimited client-side timeout using 096 * {@code SearchRequest.setResponseTimeoutMillis(0L)}.</LI> 097 * <LI>The search request should be configured with an intermediate response 098 * listener using the 099 * {@code SearchRequest.setIntermediateResponseListener} method.</LI> 100 * <LI>If the search does complete, then the 101 * {@link com.unboundid.ldap.sdk.SearchResult} (or 102 * {@link com.unboundid.ldap.sdk.LDAPSearchException} if the search ended 103 * with a non-success response) may include a 104 * {@link ContentSyncDoneControl} with updated sync state information. 105 * You should call 106 * {@code getResponseControl(ContentSyncDoneControl.SYNC_DONE_OID)} to 107 * retrieve the control with the sync state information.</LI> 108 * </UL> 109 */ 110@NotMutable() 111@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 112public final class ContentSyncRequestControl 113 extends Control 114{ 115 /** 116 * The OID (1.3.6.1.4.1.4203.1.9.1.1) for the sync request control. 117 */ 118 @NotNull public static final String SYNC_REQUEST_OID = 119 "1.3.6.1.4.1.4203.1.9.1.1"; 120 121 122 123 /** 124 * The serial version UID for this serializable class. 125 */ 126 private static final long serialVersionUID = -3183343423271667072L; 127 128 129 130 // The cookie to include in the sync request. 131 @Nullable private final ASN1OctetString cookie; 132 133 // Indicates whether to request an initial content in the event that the 134 // server determines that the client cannot reach convergence with the server 135 // data by continuing with incremental synchronization. 136 private final boolean reloadHint; 137 138 // The request mode for this control. 139 @NotNull private final ContentSyncRequestMode mode; 140 141 142 143 /** 144 * Creates a new content synchronization request control that will attempt to 145 * retrieve the initial content for the synchronization using the provided 146 * request mode. It will be marked critical. 147 * 148 * @param mode The request mode which indicates whether to retrieve only 149 * the initial content or to both retrieve the initial content 150 * and be updated of changes made in the future. It must not 151 * be {@code null}. 152 */ 153 public ContentSyncRequestControl(@NotNull final ContentSyncRequestMode mode) 154 { 155 this(true, mode, null, false); 156 } 157 158 159 160 /** 161 * Creates a new content synchronization request control that may be used to 162 * either retrieve the initial content or an incremental update. It will be 163 * marked critical. It will be marked critical. 164 * 165 * @param mode The request mode which indicates whether to retrieve 166 * only the initial content or to both retrieve the 167 * initial content and be updated of changes made in the 168 * future. It must not be {@code null}. 169 * @param cookie A cookie providing state information for an existing 170 * synchronization session. It may be {@code null} to 171 * perform an initial synchronization rather than an 172 * incremental update. 173 * @param reloadHint Indicates whether the client wishes to retrieve an 174 * initial content during an incremental update if the 175 * server determines that the client cannot reach 176 * convergence with the server data. 177 */ 178 public ContentSyncRequestControl(@NotNull final ContentSyncRequestMode mode, 179 @Nullable final ASN1OctetString cookie, 180 final boolean reloadHint) 181 { 182 this(true, mode, cookie, reloadHint); 183 } 184 185 186 187 /** 188 * Creates a new content synchronization request control that may be used to 189 * either retrieve the initial content or an incremental update. 190 * 191 * @param isCritical Indicates whether this control should be marked 192 * critical. 193 * @param mode The request mode which indicates whether to retrieve 194 * only the initial content or to both retrieve the 195 * initial content and be updated of changes made in the 196 * future. It must not be {@code null}. 197 * @param cookie A cookie providing state information for an existing 198 * synchronization session. It may be {@code null} to 199 * perform an initial synchronization rather than an 200 * incremental update. 201 * @param reloadHint Indicates whether the client wishes to retrieve an 202 * initial content during an incremental update if the 203 * server determines that the client cannot reach 204 * convergence with the server data. 205 */ 206 public ContentSyncRequestControl(final boolean isCritical, 207 @NotNull final ContentSyncRequestMode mode, 208 @Nullable final ASN1OctetString cookie, 209 final boolean reloadHint) 210 { 211 super(SYNC_REQUEST_OID, isCritical, encodeValue(mode, cookie, reloadHint)); 212 213 this.mode = mode; 214 this.cookie = cookie; 215 this.reloadHint = reloadHint; 216 } 217 218 219 220 /** 221 * Creates a new content synchronization request control which is decoded from 222 * the provided generic control. 223 * 224 * @param control The generic control to be decoded as a content 225 * synchronization request control. 226 * 227 * @throws LDAPException If the provided control cannot be decoded as a 228 * content synchronization request control. 229 */ 230 public ContentSyncRequestControl(@NotNull final Control control) 231 throws LDAPException 232 { 233 super(control); 234 235 final ASN1OctetString value = control.getValue(); 236 if (value == null) 237 { 238 throw new LDAPException(ResultCode.DECODING_ERROR, 239 ERR_SYNC_REQUEST_NO_VALUE.get()); 240 } 241 242 ASN1OctetString c = null; 243 Boolean h = null; 244 ContentSyncRequestMode m = null; 245 246 try 247 { 248 final ASN1Sequence s = ASN1Sequence.decodeAsSequence(value.getValue()); 249 for (final ASN1Element e : s.elements()) 250 { 251 switch (e.getType()) 252 { 253 case ASN1Constants.UNIVERSAL_ENUMERATED_TYPE: 254 if (m != null) 255 { 256 throw new LDAPException(ResultCode.DECODING_ERROR, 257 ERR_SYNC_REQUEST_VALUE_MULTIPLE_MODES.get()); 258 } 259 260 final ASN1Enumerated modeElement = 261 ASN1Enumerated.decodeAsEnumerated(e); 262 m = ContentSyncRequestMode.valueOf(modeElement.intValue()); 263 if (m == null) 264 { 265 throw new LDAPException(ResultCode.DECODING_ERROR, 266 ERR_SYNC_REQUEST_VALUE_INVALID_MODE.get( 267 modeElement.intValue())); 268 } 269 break; 270 271 case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE: 272 if (c == null) 273 { 274 c = ASN1OctetString.decodeAsOctetString(e); 275 } 276 else 277 { 278 throw new LDAPException(ResultCode.DECODING_ERROR, 279 ERR_SYNC_REQUEST_VALUE_MULTIPLE_COOKIES.get()); 280 } 281 break; 282 283 case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE: 284 if (h == null) 285 { 286 h = ASN1Boolean.decodeAsBoolean(e).booleanValue(); 287 } 288 else 289 { 290 throw new LDAPException(ResultCode.DECODING_ERROR, 291 ERR_SYNC_REQUEST_VALUE_MULTIPLE_HINTS.get()); 292 } 293 break; 294 295 default: 296 throw new LDAPException(ResultCode.DECODING_ERROR, 297 ERR_SYNC_REQUEST_VALUE_INVALID_ELEMENT_TYPE.get( 298 StaticUtils.toHex(e.getType()))); 299 } 300 } 301 } 302 catch (final LDAPException le) 303 { 304 throw le; 305 } 306 catch (final Exception e) 307 { 308 Debug.debugException(e); 309 310 throw new LDAPException(ResultCode.DECODING_ERROR, 311 ERR_SYNC_REQUEST_VALUE_CANNOT_DECODE.get( 312 StaticUtils.getExceptionMessage(e)), e); 313 } 314 315 if (m == null) 316 { 317 throw new LDAPException(ResultCode.DECODING_ERROR, 318 ERR_SYNC_REQUEST_VALUE_NO_MODE.get()); 319 } 320 else 321 { 322 mode = m; 323 } 324 325 if (h == null) 326 { 327 reloadHint = false; 328 } 329 else 330 { 331 reloadHint = h; 332 } 333 334 cookie = c; 335 } 336 337 338 339 /** 340 * Encodes the provided information into a form suitable for use as the value 341 * of this control. 342 * 343 * @param mode The request mode which indicates whether to retrieve 344 * only the initial content or to both retrieve the 345 * initial content and be updated of changes made in the 346 * future. It must not be {@code null}. 347 * @param cookie A cookie providing state information for an existing 348 * synchronization session. It may be {@code null} to 349 * perform an initial synchronization rather than an 350 * incremental update. 351 * @param reloadHint Indicates whether the client wishes to retrieve an 352 * initial content during an incremental update if the 353 * server determines that the client cannot reach 354 * convergence with the server data. 355 * 356 * @return An ASN.1 octet string containing the encoded control value. 357 */ 358 @NotNull() 359 private static ASN1OctetString encodeValue( 360 @NotNull final ContentSyncRequestMode mode, 361 @Nullable final ASN1OctetString cookie, 362 final boolean reloadHint) 363 { 364 Validator.ensureNotNull(mode); 365 366 final ArrayList<ASN1Element> elements = new ArrayList<>(3); 367 elements.add(new ASN1Enumerated(mode.intValue())); 368 369 if (cookie != null) 370 { 371 elements.add(cookie); 372 } 373 374 if (reloadHint) 375 { 376 elements.add(ASN1Boolean.UNIVERSAL_BOOLEAN_TRUE_ELEMENT); 377 } 378 379 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 380 } 381 382 383 384 /** 385 * Retrieves the mode for this content synchronization request control, which 386 * indicates whether to retrieve an initial content or an incremental update. 387 * 388 * @return The mode for this content synchronization request control. 389 */ 390 @NotNull() 391 public ContentSyncRequestMode getMode() 392 { 393 return mode; 394 } 395 396 397 398 /** 399 * Retrieves a cookie providing state information for an existing 400 * synchronization session, if available. 401 * 402 * @return A cookie providing state information for an existing 403 * synchronization session, or {@code null} if none is available and 404 * an initial content should be retrieved. 405 */ 406 @Nullable() 407 public ASN1OctetString getCookie() 408 { 409 return cookie; 410 } 411 412 413 414 /** 415 * Retrieves the reload hint value for this synchronization request control. 416 * 417 * @return {@code true} if the server should return an initial content rather 418 * than an incremental update if it determines that the client cannot 419 * reach convergence, or {@code false} if it should return an 420 * e-sync refresh required result in that case. 421 */ 422 public boolean getReloadHint() 423 { 424 return reloadHint; 425 } 426 427 428 429 /** 430 * {@inheritDoc} 431 */ 432 @Override() 433 @NotNull() 434 public String getControlName() 435 { 436 return INFO_CONTROL_NAME_CONTENT_SYNC_REQUEST.get(); 437 } 438 439 440 441 /** 442 * {@inheritDoc} 443 */ 444 @Override() 445 public void toString(@NotNull final StringBuilder buffer) 446 { 447 buffer.append("ContentSyncRequestControl(mode='"); 448 buffer.append(mode.name()); 449 buffer.append('\''); 450 451 if (cookie != null) 452 { 453 buffer.append(", cookie='"); 454 StaticUtils.toHex(cookie.getValue(), buffer); 455 buffer.append('\''); 456 } 457 458 buffer.append(", reloadHint="); 459 buffer.append(reloadHint); 460 buffer.append(')'); 461 } 462}