001    /*
002     * Copyright 2010-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2010-2015 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    }