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.text.ParseException;
026    import java.util.ArrayList;
027    import java.util.UUID;
028    
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.DecodeableControl;
035    import com.unboundid.ldap.sdk.LDAPException;
036    import com.unboundid.ldap.sdk.ResultCode;
037    import com.unboundid.ldap.sdk.SearchResultEntry;
038    import com.unboundid.ldap.sdk.SearchResultReference;
039    import com.unboundid.util.Debug;
040    import com.unboundid.util.NotMutable;
041    import com.unboundid.util.StaticUtils;
042    import com.unboundid.util.ThreadSafety;
043    import com.unboundid.util.ThreadSafetyLevel;
044    import com.unboundid.util.Validator;
045    
046    import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
047    
048    
049    
050    /**
051     * This class provides an implementation of the LDAP content synchronization
052     * state control as defined in
053     * <a href="http://www.ietf.org/rfc/rfc4533.txt">RFC 4533</a>.  Directory
054     * servers may include this control in search result entry and search result
055     * reference messages returned for a search request containing the content
056     * synchronization request control.  See the documentation for the
057     * {@link ContentSyncRequestControl} class for more information information
058     * about using the content synchronization operation.
059     */
060    @NotMutable()
061    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
062    public final class ContentSyncStateControl
063           extends Control
064           implements DecodeableControl
065    {
066      /**
067       * The OID (1.3.6.1.4.1.4203.1.9.1.2) for the sync state control.
068       */
069      public static final String SYNC_STATE_OID = "1.3.6.1.4.1.4203.1.9.1.2";
070    
071    
072    
073      /**
074       * The serial version UID for this serializable class.
075       */
076      private static final long serialVersionUID = 4796325788870542241L;
077    
078    
079    
080      // The synchronization state cookie.
081      private final ASN1OctetString cookie;
082    
083      // The synchronization state for the associated entry.
084      private final ContentSyncState state;
085    
086      // The entryUUID value for the associated entry.
087      private final UUID entryUUID;
088    
089    
090    
091      /**
092       * Creates a new empty control instance that is intended to be used only for
093       * decoding controls via the {@code DecodeableControl} interface.
094       */
095      ContentSyncStateControl()
096      {
097        state     = null;
098        entryUUID = null;
099        cookie    = null;
100      }
101    
102    
103    
104      /**
105       * Creates a new content synchronization state control that provides
106       * information about a search result entry or referenced returned by a search
107       * containing the content synchronization request control.
108       *
109       * @param  state      The sync state for the associated entry or reference.
110       *                    It must not be {@code null}.
111       * @param  entryUUID  The entryUUID for the associated entry or reference.  It
112       *                    must not be {@code null}.
113       * @param  cookie     A cookie with an updated synchronization state.  It may
114       *                    be {@code null} if no updated state is available.
115       */
116      public ContentSyncStateControl(final ContentSyncState state,
117                                     final UUID entryUUID,
118                                     final ASN1OctetString cookie)
119      {
120        super(SYNC_STATE_OID, false, encodeValue(state, entryUUID, cookie));
121    
122        this.state     = state;
123        this.entryUUID = entryUUID;
124        this.cookie    = cookie;
125      }
126    
127    
128    
129      /**
130       * Creates a new content synchronization state control which is decoded from
131       * the provided information from a generic control.
132       *
133       * @param  oid         The OID for the control used to create this control.
134       * @param  isCritical  Indicates whether the control is marked critical.
135       * @param  value       The encoded value for the control.
136       *
137       * @throws  LDAPException  If the provided control cannot be decoded as a
138       *                         content synchronization state control.
139       */
140      public ContentSyncStateControl(final String oid, final boolean isCritical,
141                                     final ASN1OctetString value)
142             throws LDAPException
143      {
144        super(oid, isCritical, value);
145    
146        if (value == null)
147        {
148          throw new LDAPException(ResultCode.DECODING_ERROR,
149               ERR_SYNC_STATE_NO_VALUE.get());
150        }
151    
152        try
153        {
154          final ASN1Element[] elements =
155               ASN1Sequence.decodeAsSequence(value.getValue()).elements();
156    
157          final ASN1Enumerated e = ASN1Enumerated.decodeAsEnumerated(elements[0]);
158          state = ContentSyncState.valueOf(e.intValue());
159          if (state == null)
160          {
161            throw new LDAPException(ResultCode.DECODING_ERROR,
162                 ERR_SYNC_STATE_VALUE_INVALID_STATE.get(e.intValue()));
163          }
164    
165          try
166          {
167            entryUUID = StaticUtils.decodeUUID(elements[1].getValue());
168          }
169          catch (final ParseException pe)
170          {
171            Debug.debugException(pe);
172            throw new LDAPException(ResultCode.DECODING_ERROR,
173                 ERR_SYNC_STATE_VALUE_MALFORMED_UUID.get(pe.getMessage()), pe);
174          }
175    
176          if (elements.length == 3)
177          {
178            cookie = ASN1OctetString.decodeAsOctetString(elements[2]);
179          }
180          else
181          {
182            cookie = null;
183          }
184        }
185        catch (final LDAPException le)
186        {
187          throw le;
188        }
189        catch (final Exception e)
190        {
191          Debug.debugException(e);
192    
193          throw new LDAPException(ResultCode.DECODING_ERROR,
194               ERR_SYNC_STATE_VALUE_CANNOT_DECODE.get(
195                    StaticUtils.getExceptionMessage(e)), e);
196        }
197      }
198    
199    
200    
201      /**
202       * Encodes the provided information into a form suitable for use as the value
203       * of this control.
204       *
205       * @param  state      The sync state for the associated entry or reference.
206       *                    It must not be {@code null}.
207       * @param  entryUUID  The entryUUID for the associated entry or reference.  It
208       *                    must not be {@code null}.
209       * @param  cookie     A cookie with an updated synchronization state.  It may
210       *                    be {@code null} if no updated state is available.
211       *
212       * @return  An ASN.1 octet string containing the encoded control value.
213       */
214      private static ASN1OctetString encodeValue(final ContentSyncState state,
215                                                 final UUID entryUUID,
216                                                 final ASN1OctetString cookie)
217      {
218        Validator.ensureNotNull(state, entryUUID);
219    
220        final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(3);
221        elements.add(new ASN1Enumerated(state.intValue()));
222        elements.add(new ASN1OctetString(StaticUtils.encodeUUID(entryUUID)));
223    
224        if (cookie != null)
225        {
226          elements.add(cookie);
227        }
228    
229        return new ASN1OctetString(new ASN1Sequence(elements).encode());
230      }
231    
232    
233    
234      /**
235       * {@inheritDoc}
236       */
237      public ContentSyncStateControl decodeControl(final String oid,
238                                                   final boolean isCritical,
239                                                   final ASN1OctetString value)
240             throws LDAPException
241      {
242        return new ContentSyncStateControl(oid, isCritical, value);
243      }
244    
245    
246    
247      /**
248       * Extracts a content sync state control from the provided search result
249       * entry.
250       *
251       * @param  entry  The search result entry from which to retrieve the content
252       *                sync state control.
253       *
254       * @return  The content sync state control contained in the provided search
255       *          result entry, or {@code null} if the entry did not contain a
256       *          content sync state control.
257       *
258       * @throws  LDAPException  If a problem is encountered while attempting to
259       *                         decode the content sync state control contained in
260       *                         the provided search result entry.
261       */
262      public static ContentSyncStateControl get(final SearchResultEntry entry)
263             throws LDAPException
264      {
265        final Control c = entry.getControl(SYNC_STATE_OID);
266        if (c == null)
267        {
268          return null;
269        }
270    
271        if (c instanceof ContentSyncStateControl)
272        {
273          return (ContentSyncStateControl) c;
274        }
275        else
276        {
277          return new ContentSyncStateControl(c.getOID(), c.isCritical(),
278               c.getValue());
279        }
280      }
281    
282    
283    
284      /**
285       * Extracts a content sync state control from the provided search result
286       * reference.
287       *
288       * @param  ref  The search result reference from which to retrieve the content
289       *              sync state control.
290       *
291       * @return  The content sync state control contained in the provided search
292       *          result reference, or {@code null} if the reference did not contain
293       *          a content sync state control.
294       *
295       * @throws  LDAPException  If a problem is encountered while attempting to
296       *                         decode the content sync state control contained in
297       *                         the provided search result reference.
298       */
299      public static ContentSyncStateControl get(final SearchResultReference ref)
300             throws LDAPException
301      {
302        final Control c = ref.getControl(SYNC_STATE_OID);
303        if (c == null)
304        {
305          return null;
306        }
307    
308        if (c instanceof ContentSyncStateControl)
309        {
310          return (ContentSyncStateControl) c;
311        }
312        else
313        {
314          return new ContentSyncStateControl(c.getOID(), c.isCritical(),
315               c.getValue());
316        }
317      }
318    
319    
320    
321      /**
322       * Retrieves the synchronization state for this control, which provides
323       * information about the state of the associated search result entry or
324       * reference.
325       *
326       * @return  The state value for this content synchronization state control.
327       */
328      public ContentSyncState getState()
329      {
330        return state;
331      }
332    
333    
334    
335      /**
336       * Retrieves the entryUUID for the associated search result entry or
337       * reference.
338       *
339       * @return  The entryUUID for the associated search result entry or
340       *          reference.
341       */
342      public UUID getEntryUUID()
343      {
344        return entryUUID;
345      }
346    
347    
348    
349      /**
350       * Retrieves a cookie providing updated state information for the
351       * synchronization session, if available.
352       *
353       * @return  A cookie providing updated state information for the
354       *          synchronization session, or {@code null} if none was included in
355       *          the control.
356       */
357      public ASN1OctetString getCookie()
358      {
359        return cookie;
360      }
361    
362    
363    
364      /**
365       * {@inheritDoc}
366       */
367      @Override()
368      public String getControlName()
369      {
370        return INFO_CONTROL_NAME_CONTENT_SYNC_STATE.get();
371      }
372    
373    
374    
375      /**
376       * {@inheritDoc}
377       */
378      @Override()
379      public void toString(final StringBuilder buffer)
380      {
381        buffer.append("ContentSyncStateControl(state='");
382        buffer.append(state.name());
383        buffer.append("', entryUUID='");
384        buffer.append(entryUUID);
385        buffer.append('\'');
386    
387        if (cookie != null)
388        {
389          buffer.append(", cookie=");
390          StaticUtils.toHex(cookie.getValue(), buffer);
391        }
392    
393        buffer.append(')');
394      }
395    }