001/*
002 * Copyright 2010-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2010-2017 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.UUID;
028
029import com.unboundid.asn1.ASN1Element;
030import com.unboundid.asn1.ASN1Enumerated;
031import com.unboundid.asn1.ASN1OctetString;
032import com.unboundid.asn1.ASN1Sequence;
033import com.unboundid.ldap.sdk.Control;
034import com.unboundid.ldap.sdk.DecodeableControl;
035import com.unboundid.ldap.sdk.LDAPException;
036import com.unboundid.ldap.sdk.ResultCode;
037import com.unboundid.ldap.sdk.SearchResultEntry;
038import com.unboundid.ldap.sdk.SearchResultReference;
039import com.unboundid.util.Debug;
040import com.unboundid.util.NotMutable;
041import com.unboundid.util.StaticUtils;
042import com.unboundid.util.ThreadSafety;
043import com.unboundid.util.ThreadSafetyLevel;
044import com.unboundid.util.Validator;
045
046import 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)
062public 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}