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