001 /*
002 * Copyright 2010-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2010-2014 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 */
021 package com.unboundid.ldap.sdk.controls;
022
023
024
025 import java.util.ArrayList;
026
027 import com.unboundid.asn1.ASN1Boolean;
028 import com.unboundid.asn1.ASN1Constants;
029 import com.unboundid.asn1.ASN1Element;
030 import com.unboundid.asn1.ASN1Enumerated;
031 import com.unboundid.asn1.ASN1OctetString;
032 import com.unboundid.asn1.ASN1Sequence;
033 import com.unboundid.ldap.sdk.Control;
034 import com.unboundid.ldap.sdk.LDAPException;
035 import com.unboundid.ldap.sdk.ResultCode;
036 import com.unboundid.util.Debug;
037 import com.unboundid.util.NotMutable;
038 import com.unboundid.util.StaticUtils;
039 import com.unboundid.util.ThreadSafety;
040 import com.unboundid.util.ThreadSafetyLevel;
041 import com.unboundid.util.Validator;
042
043 import 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)
095 public 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 }