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 */
021package com.unboundid.ldap.sdk.controls;
022
023
024
025import java.text.ParseException;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.Iterator;
029import java.util.List;
030import java.util.UUID;
031
032import com.unboundid.asn1.ASN1Boolean;
033import com.unboundid.asn1.ASN1Constants;
034import com.unboundid.asn1.ASN1Element;
035import com.unboundid.asn1.ASN1OctetString;
036import com.unboundid.asn1.ASN1Sequence;
037import com.unboundid.asn1.ASN1Set;
038import com.unboundid.ldap.sdk.Control;
039import com.unboundid.ldap.sdk.IntermediateResponse;
040import com.unboundid.ldap.sdk.LDAPException;
041import com.unboundid.ldap.sdk.ResultCode;
042import com.unboundid.util.Debug;
043import com.unboundid.util.NotMutable;
044import com.unboundid.util.StaticUtils;
045import com.unboundid.util.ThreadSafety;
046import com.unboundid.util.ThreadSafetyLevel;
047import com.unboundid.util.Validator;
048
049import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
050
051
052
053/**
054 * This class provides an implementation of the sync info message, which is
055 * an intermediate response message used by the content synchronization
056 * operation as defined in
057 * <a href="http://www.ietf.org/rfc/rfc4533.txt">RFC 4533</a>.  Directory
058 * servers may return this response in the course of processing a search
059 * request containing the content synchronization request control.  See the
060 * documentation for the {@link ContentSyncRequestControl} class for more
061 * information about using the content synchronization operation.
062 */
063@NotMutable()
064@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
065public final class ContentSyncInfoIntermediateResponse
066       extends IntermediateResponse
067{
068  /**
069   * The OID (1.3.6.1.4.1.4203.1.9.1.4) for the sync info intermediate response.
070   */
071  public static final String SYNC_INFO_OID = "1.3.6.1.4.1.4203.1.9.1.4";
072
073
074
075  /**
076   * The serial version UID for this serializable class.
077   */
078  private static final long serialVersionUID = 4464376009337157433L;
079
080
081
082  // An updated state cookie, if available.
083  private final ASN1OctetString cookie;
084
085  // Indicates whether the provided set of UUIDs represent entries that have
086  // been removed.
087  private final boolean refreshDeletes;
088
089  // Indicates whether the refresh phase is complete.
090  private final boolean refreshDone;
091
092  // The type of content synchronization information represented in this
093  // response.
094  private final ContentSyncInfoType type;
095
096  // A list of entryUUIDs for the set of entries associated with this message.
097  private final List<UUID> entryUUIDs;
098
099
100
101  /**
102   * Creates a new content synchronization info intermediate response with the
103   * provided information.
104   *
105   * @param  type            The type of content synchronization information
106   *                         represented in this response.
107   * @param  value           The encoded value for the intermediate response, if
108   *                         any.
109   * @param  cookie          An updated state cookie for the synchronization
110   *                         session, if available.
111   * @param  refreshDone     Indicates whether the refresh phase of the
112   *                         synchronization session is complete.
113   * @param  refreshDeletes  Indicates whether the provided set of UUIDs
114   *                         represent entries that have been removed.
115   * @param  entryUUIDs      A list of entryUUIDs for the set of entries
116   *                         associated with this message.
117   * @param  controls        The set of controls to include in the intermediate
118   *                         response, if any.
119   */
120  private ContentSyncInfoIntermediateResponse(final ContentSyncInfoType type,
121                 final ASN1OctetString value, final ASN1OctetString cookie,
122                 final boolean refreshDone, final boolean refreshDeletes,
123                 final List<UUID> entryUUIDs, final Control... controls)
124  {
125    super(SYNC_INFO_OID, value, controls);
126
127    this.type           = type;
128    this.cookie         = cookie;
129    this.refreshDone    = refreshDone;
130    this.refreshDeletes = refreshDeletes;
131    this.entryUUIDs     = entryUUIDs;
132  }
133
134
135
136  /**
137   * Creates a new sync info intermediate response with a type of
138   * {@link ContentSyncInfoType#NEW_COOKIE}.
139   *
140   * @param  cookie    The updated state cookie for the synchronization session.
141   *                   It must not be {@code null}.
142   * @param  controls  An optional set of controls to include in the response.
143   *                   It may be {@code null} or empty if no controls should be
144   *                   included.
145   *
146   * @return  The created sync info intermediate response.
147   */
148  public static ContentSyncInfoIntermediateResponse createNewCookieResponse(
149                     final ASN1OctetString cookie, final Control... controls)
150  {
151    Validator.ensureNotNull(cookie);
152
153    final ContentSyncInfoType type = ContentSyncInfoType.NEW_COOKIE;
154
155    return new ContentSyncInfoIntermediateResponse(type,
156         encodeValue(type, cookie, false, null, false),
157         cookie, false, false, null, controls);
158  }
159
160
161
162  /**
163   * Creates a new sync info intermediate response with a type of
164   * {@link ContentSyncInfoType#REFRESH_DELETE}.
165   *
166   * @param  cookie       The updated state cookie for the synchronization
167   *                      session.  It may be {@code null} if no new cookie is
168   *                      available.
169   * @param  refreshDone  Indicates whether the refresh phase of the
170   *                      synchronization operation has completed.
171   * @param  controls     An optional set of controls to include in the
172   *                      response.  It may be {@code null} or empty if no
173   *                      controls should be included.
174   *
175   * @return  The created sync info intermediate response.
176   */
177  public static ContentSyncInfoIntermediateResponse createRefreshDeleteResponse(
178                     final ASN1OctetString cookie, final boolean refreshDone,
179                     final Control... controls)
180  {
181    final ContentSyncInfoType type = ContentSyncInfoType.REFRESH_DELETE;
182
183    return new ContentSyncInfoIntermediateResponse(type,
184         encodeValue(type, cookie, refreshDone, null, false),
185         cookie, refreshDone, false, null, controls);
186  }
187
188
189
190  /**
191   * Creates a new sync info intermediate response with a type of
192   * {@link ContentSyncInfoType#REFRESH_PRESENT}.
193   *
194   * @param  cookie       The updated state cookie for the synchronization
195   *                      session.  It may be {@code null} if no new cookie is
196   *                      available.
197   * @param  refreshDone  Indicates whether the refresh phase of the
198   *                      synchronization operation has completed.
199   * @param  controls     An optional set of controls to include in the
200   *                      response.  It may be {@code null} or empty if no
201   *                      controls should be included.
202   *
203   * @return  The created sync info intermediate response.
204   */
205  public static ContentSyncInfoIntermediateResponse
206                     createRefreshPresentResponse(final ASN1OctetString cookie,
207                                                  final boolean refreshDone,
208                                                  final Control... controls)
209  {
210    final ContentSyncInfoType type = ContentSyncInfoType.REFRESH_PRESENT;
211
212    return new ContentSyncInfoIntermediateResponse(type,
213         encodeValue(type, cookie, refreshDone, null, false),
214         cookie, refreshDone, false, null, controls);
215  }
216
217
218
219  /**
220   * Creates a new sync info intermediate response with a type of
221   * {@link ContentSyncInfoType#SYNC_ID_SET}.
222   *
223   * @param  cookie          The updated state cookie for the synchronization
224   *                         session.  It may be {@code null} if no new cookie
225   *                         is available.
226   * @param  entryUUIDs      The set of entryUUIDs for the entries referenced in
227   *                         this response.  It must not be {@code null}.
228   * @param  refreshDeletes  Indicates whether the entryUUIDs represent entries
229   *                         that have been removed rather than those that have
230   *                         remained unchanged.
231   * @param  controls        An optional set of controls to include in the
232   *                         response.  It may be {@code null} or empty if no
233   *                         controls should be included.
234   *
235   * @return  The created sync info intermediate response.
236   */
237  public static ContentSyncInfoIntermediateResponse createSyncIDSetResponse(
238                     final ASN1OctetString cookie, final List<UUID> entryUUIDs,
239                     final boolean refreshDeletes, final Control... controls)
240  {
241    Validator.ensureNotNull(entryUUIDs);
242
243    final ContentSyncInfoType type = ContentSyncInfoType.SYNC_ID_SET;
244
245    return new ContentSyncInfoIntermediateResponse(type,
246         encodeValue(type, cookie, false, entryUUIDs, refreshDeletes),
247         cookie, false, refreshDeletes,
248         Collections.unmodifiableList(entryUUIDs), controls);
249  }
250
251
252
253  /**
254   * Decodes the provided generic intermediate response as a sync info
255   * intermediate response.
256   *
257   * @param  r  The intermediate response to be decoded as a sync info
258   *            intermediate response.  It must not be {@code null}.
259   *
260   * @return  The decoded sync info intermediate response.
261   *
262   * @throws  LDAPException  If a problem occurs while trying to decode the
263   *                         provided intermediate response as a sync info
264   *                         response.
265   */
266  public static ContentSyncInfoIntermediateResponse decode(
267                     final IntermediateResponse r)
268         throws LDAPException
269  {
270    final ASN1OctetString value = r.getValue();
271    if (value == null)
272    {
273      throw new LDAPException(ResultCode.DECODING_ERROR,
274           ERR_SYNC_INFO_IR_NO_VALUE.get());
275    }
276
277    final ASN1Element valueElement;
278    try
279    {
280      valueElement = ASN1Element.decode(value.getValue());
281    }
282    catch (final Exception e)
283    {
284      Debug.debugException(e);
285
286      throw new LDAPException(ResultCode.DECODING_ERROR,
287           ERR_SYNC_INFO_IR_VALUE_NOT_ELEMENT.get(
288                StaticUtils.getExceptionMessage(e)), e);
289    }
290
291    final ContentSyncInfoType type =
292         ContentSyncInfoType.valueOf(valueElement.getType());
293    if (type == null)
294    {
295      throw new LDAPException(ResultCode.DECODING_ERROR,
296           ERR_SYNC_INFO_IR_VALUE_UNRECOGNIZED_TYPE.get(
297                StaticUtils.toHex(valueElement.getType())));
298    }
299
300    ASN1OctetString cookie         = null;
301    boolean         refreshDone    = false;
302    boolean         refreshDeletes = false;
303    List<UUID>      entryUUIDs     = null;
304
305    try
306    {
307      switch (type)
308      {
309        case NEW_COOKIE:
310          cookie = new ASN1OctetString(valueElement.getValue());
311          break;
312
313        case REFRESH_DELETE:
314        case REFRESH_PRESENT:
315          refreshDone = true;
316
317          ASN1Sequence s = valueElement.decodeAsSequence();
318          for (final ASN1Element e : s.elements())
319          {
320            switch (e.getType())
321            {
322              case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE:
323                cookie = ASN1OctetString.decodeAsOctetString(e);
324                break;
325              case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE:
326                refreshDone = ASN1Boolean.decodeAsBoolean(e).booleanValue();
327                break;
328              default:
329                throw new LDAPException(ResultCode.DECODING_ERROR,
330                     ERR_SYNC_INFO_IR_VALUE_INVALID_SEQUENCE_TYPE.get(
331                          type.name(), StaticUtils.toHex(e.getType())));
332            }
333          }
334          break;
335
336        case SYNC_ID_SET:
337          s = valueElement.decodeAsSequence();
338          for (final ASN1Element e : s.elements())
339          {
340            switch (e.getType())
341            {
342              case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE:
343                cookie = ASN1OctetString.decodeAsOctetString(e);
344                break;
345              case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE:
346                refreshDeletes = ASN1Boolean.decodeAsBoolean(e).booleanValue();
347                break;
348              case ASN1Constants.UNIVERSAL_SET_TYPE:
349                final ASN1Set uuidSet = ASN1Set.decodeAsSet(e);
350                final ASN1Element[] uuidElements = uuidSet.elements();
351                entryUUIDs = new ArrayList<UUID>(uuidElements.length);
352                for (final ASN1Element uuidElement : uuidElements)
353                {
354                  try
355                  {
356                    entryUUIDs.add(StaticUtils.decodeUUID(
357                         uuidElement.getValue()));
358                  }
359                  catch (final ParseException pe)
360                  {
361                    Debug.debugException(pe);
362                    throw new LDAPException(ResultCode.DECODING_ERROR,
363                         ERR_SYNC_INFO_IR_INVALID_UUID.get(type.name(),
364                              pe.getMessage()), pe);
365                  }
366                }
367                break;
368              default:
369                throw new LDAPException(ResultCode.DECODING_ERROR,
370                     ERR_SYNC_INFO_IR_VALUE_INVALID_SEQUENCE_TYPE.get(
371                          type.name(), StaticUtils.toHex(e.getType())));
372            }
373          }
374
375          if (entryUUIDs == null)
376          {
377            throw new LDAPException(ResultCode.DECODING_ERROR,
378                 ERR_SYNC_INFO_IR_NO_UUID_SET.get(type.name()));
379          }
380          break;
381      }
382    }
383    catch (final LDAPException le)
384    {
385      throw le;
386    }
387    catch (final Exception e)
388    {
389      Debug.debugException(e);
390
391      throw new LDAPException(ResultCode.DECODING_ERROR,
392           ERR_SYNC_INFO_IR_VALUE_DECODING_ERROR.get(
393                StaticUtils.getExceptionMessage(e)), e);
394    }
395
396    return new ContentSyncInfoIntermediateResponse(type, value, cookie,
397         refreshDone, refreshDeletes, entryUUIDs, r.getControls());
398  }
399
400
401
402  /**
403   * Encodes the provided information into a form suitable for use as the value
404   * of this intermediate response.
405   *
406   * @param  type            The type for this sync info message.
407   * @param  cookie          The updated sync state cookie.
408   * @param  refreshDone     Indicates whether the refresh phase of the
409   *                         synchronization operation is complete.
410   * @param  entryUUIDs      The set of entryUUIDs for the entries referenced
411   *                         in this message.
412   * @param  refreshDeletes  Indicates whether the associated entryUUIDs are for
413   *                         entries that have been removed.
414   *
415   * @return  The encoded value.
416   */
417  private static ASN1OctetString encodeValue(final ContentSyncInfoType type,
418                                             final ASN1OctetString cookie,
419                                             final boolean refreshDone,
420                                             final List<UUID> entryUUIDs,
421                                             final boolean refreshDeletes)
422  {
423    final ASN1Element e;
424    switch (type)
425    {
426      case NEW_COOKIE:
427        e = new ASN1OctetString(type.getType(), cookie.getValue());
428        break;
429
430      case REFRESH_DELETE:
431      case REFRESH_PRESENT:
432        ArrayList<ASN1Element> l = new ArrayList<ASN1Element>(2);
433        if (cookie != null)
434        {
435          l.add(cookie);
436        }
437
438        if (! refreshDone)
439        {
440          l.add(new ASN1Boolean(refreshDone));
441        }
442
443        e = new ASN1Sequence(type.getType(), l);
444        break;
445
446      case SYNC_ID_SET:
447        l = new ArrayList<ASN1Element>(3);
448
449        if (cookie != null)
450        {
451          l.add(cookie);
452        }
453
454        if (refreshDeletes)
455        {
456          l.add(new ASN1Boolean(refreshDeletes));
457        }
458
459        final ArrayList<ASN1Element> uuidElements =
460             new ArrayList<ASN1Element>(entryUUIDs.size());
461        for (final UUID uuid : entryUUIDs)
462        {
463          uuidElements.add(new ASN1OctetString(StaticUtils.encodeUUID(uuid)));
464        }
465        l.add(new ASN1Set(uuidElements));
466
467        e = new ASN1Sequence(type.getType(), l);
468        break;
469
470      default:
471        // This should never happen.
472        throw new AssertionError("Unexpected sync info type:  " + type.name());
473    }
474
475    return new ASN1OctetString(e.encode());
476  }
477
478
479
480  /**
481   * Retrieves the type of content synchronization information represented in
482   * this response.
483   *
484   * @return  The type of content synchronization information represented in
485   *          this response.
486   */
487  public ContentSyncInfoType getType()
488  {
489    return type;
490  }
491
492
493
494  /**
495   * Retrieves an updated state cookie for the synchronization session, if
496   * available.  It will always be non-{@code null} for a type of
497   * {@link ContentSyncInfoType#NEW_COOKIE}, and may or may not be {@code null}
498   * for other types.
499   *
500   * @return  An updated state cookie for the synchronization session, or
501   *          {@code null} if none is available.
502   */
503  public ASN1OctetString getCookie()
504  {
505    return cookie;
506  }
507
508
509
510  /**
511   * Indicates whether the refresh phase of the synchronization operation has
512   * completed.  This is only applicable for the
513   * {@link ContentSyncInfoType#REFRESH_DELETE} and
514   * {@link ContentSyncInfoType#REFRESH_PRESENT} types.
515   *
516   * @return  {@code true} if the refresh phase of the synchronization operation
517   *          has completed, or {@code false} if not or if it is not applicable
518   *          for this message type.
519   */
520  public boolean refreshDone()
521  {
522    return refreshDone;
523  }
524
525
526
527  /**
528   * Retrieves a list of the entryUUID values for the entries referenced in this
529   * message.  This is only applicable for the
530   * {@link ContentSyncInfoType#SYNC_ID_SET} type.
531   *
532   * @return  A list of the entryUUID values for the entries referenced in this
533   *          message, or {@code null} if it is not applicable for this message
534   *          type.
535   */
536  public List<UUID> getEntryUUIDs()
537  {
538    return entryUUIDs;
539  }
540
541
542
543  /**
544   * Indicates whether the provided set of UUIDs represent entries that have
545   * been removed.  This is only applicable for the
546   * {@link ContentSyncInfoType#SYNC_ID_SET} type.
547   *
548   * @return  {@code true} if the associated set of entryUUIDs represent entries
549   *          that have been deleted, or {@code false} if they represent entries
550   *          that remain unchanged or if it is not applicable for this message
551   *          type.
552   */
553  public boolean refreshDeletes()
554  {
555    return refreshDeletes;
556  }
557
558
559
560  /**
561   * {@inheritDoc}
562   */
563  @Override()
564  public String getIntermediateResponseName()
565  {
566    return INFO_INTERMEDIATE_RESPONSE_NAME_SYNC_INFO.get();
567  }
568
569
570
571  /**
572   * {@inheritDoc}
573   */
574  @Override()
575  public String valueToString()
576  {
577    final StringBuilder buffer = new StringBuilder();
578
579    buffer.append("syncInfoType='");
580    buffer.append(type.name());
581    buffer.append('\'');
582
583    if (cookie != null)
584    {
585      buffer.append(" cookie='");
586      StaticUtils.toHex(cookie.getValue(), buffer);
587      buffer.append('\'');
588    }
589
590    switch (type)
591    {
592      case REFRESH_DELETE:
593      case REFRESH_PRESENT:
594        buffer.append(" refreshDone='");
595        buffer.append(refreshDone);
596        buffer.append('\'');
597        break;
598
599      case SYNC_ID_SET:
600        buffer.append(" entryUUIDs={");
601
602        final Iterator<UUID> iterator = entryUUIDs.iterator();
603        while (iterator.hasNext())
604        {
605          buffer.append('\'');
606          buffer.append(iterator.next().toString());
607          buffer.append('\'');
608
609          if (iterator.hasNext())
610          {
611            buffer.append(',');
612          }
613        }
614
615        buffer.append('}');
616        break;
617
618      case NEW_COOKIE:
619      default:
620        // No additional content is needed.
621        break;
622    }
623
624    return buffer.toString();
625  }
626
627
628
629  /**
630   * {@inheritDoc}
631   */
632  @Override()
633  public void toString(final StringBuilder buffer)
634  {
635    buffer.append("ContentSyncInfoIntermediateResponse(");
636
637    final int messageID = getMessageID();
638    if (messageID >= 0)
639    {
640      buffer.append("messageID=");
641      buffer.append(messageID);
642      buffer.append(", ");
643    }
644
645    buffer.append("type='");
646    buffer.append(type.name());
647    buffer.append('\'');
648
649    if (cookie != null)
650    {
651      buffer.append(", cookie='");
652      StaticUtils.toHex(cookie.getValue(), buffer);
653      buffer.append("', ");
654    }
655
656    switch (type)
657    {
658      case NEW_COOKIE:
659        // No additional content is needed.
660        break;
661
662      case REFRESH_DELETE:
663      case REFRESH_PRESENT:
664        buffer.append(", refreshDone=");
665        buffer.append(refreshDone);
666        break;
667
668      case SYNC_ID_SET:
669        buffer.append(", entryUUIDs={");
670
671        final Iterator<UUID> iterator = entryUUIDs.iterator();
672        while (iterator.hasNext())
673        {
674          buffer.append('\'');
675          buffer.append(iterator.next());
676          buffer.append('\'');
677          if (iterator.hasNext())
678          {
679            buffer.append(',');
680          }
681        }
682
683        buffer.append("}, refreshDeletes=");
684        buffer.append(refreshDeletes);
685        break;
686    }
687
688    buffer.append(')');
689  }
690}