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