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