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}