001    /*
002     * Copyright 2010-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2010-2016 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.text.ParseException;
026    import java.util.ArrayList;
027    import java.util.Collections;
028    import java.util.Iterator;
029    import java.util.List;
030    import java.util.UUID;
031    
032    import com.unboundid.asn1.ASN1Boolean;
033    import com.unboundid.asn1.ASN1Constants;
034    import com.unboundid.asn1.ASN1Element;
035    import com.unboundid.asn1.ASN1OctetString;
036    import com.unboundid.asn1.ASN1Sequence;
037    import com.unboundid.asn1.ASN1Set;
038    import com.unboundid.ldap.sdk.Control;
039    import com.unboundid.ldap.sdk.IntermediateResponse;
040    import com.unboundid.ldap.sdk.LDAPException;
041    import com.unboundid.ldap.sdk.ResultCode;
042    import com.unboundid.util.Debug;
043    import com.unboundid.util.NotMutable;
044    import com.unboundid.util.StaticUtils;
045    import com.unboundid.util.ThreadSafety;
046    import com.unboundid.util.ThreadSafetyLevel;
047    import com.unboundid.util.Validator;
048    
049    import 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)
065    public 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    }