001/*
002 * Copyright 2010-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2010-2018 Ping Identity Corporation
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<>(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  @Override()
238  public ContentSyncStateControl decodeControl(final String oid,
239                                               final boolean isCritical,
240                                               final ASN1OctetString value)
241         throws LDAPException
242  {
243    return new ContentSyncStateControl(oid, isCritical, value);
244  }
245
246
247
248  /**
249   * Extracts a content sync state control from the provided search result
250   * entry.
251   *
252   * @param  entry  The search result entry from which to retrieve the content
253   *                sync state control.
254   *
255   * @return  The content sync state control contained in the provided search
256   *          result entry, or {@code null} if the entry did not contain a
257   *          content sync state control.
258   *
259   * @throws  LDAPException  If a problem is encountered while attempting to
260   *                         decode the content sync state control contained in
261   *                         the provided search result entry.
262   */
263  public static ContentSyncStateControl get(final SearchResultEntry entry)
264         throws LDAPException
265  {
266    final Control c = entry.getControl(SYNC_STATE_OID);
267    if (c == null)
268    {
269      return null;
270    }
271
272    if (c instanceof ContentSyncStateControl)
273    {
274      return (ContentSyncStateControl) c;
275    }
276    else
277    {
278      return new ContentSyncStateControl(c.getOID(), c.isCritical(),
279           c.getValue());
280    }
281  }
282
283
284
285  /**
286   * Extracts a content sync state control from the provided search result
287   * reference.
288   *
289   * @param  ref  The search result reference from which to retrieve the content
290   *              sync state control.
291   *
292   * @return  The content sync state control contained in the provided search
293   *          result reference, or {@code null} if the reference did not contain
294   *          a content sync state control.
295   *
296   * @throws  LDAPException  If a problem is encountered while attempting to
297   *                         decode the content sync state control contained in
298   *                         the provided search result reference.
299   */
300  public static ContentSyncStateControl get(final SearchResultReference ref)
301         throws LDAPException
302  {
303    final Control c = ref.getControl(SYNC_STATE_OID);
304    if (c == null)
305    {
306      return null;
307    }
308
309    if (c instanceof ContentSyncStateControl)
310    {
311      return (ContentSyncStateControl) c;
312    }
313    else
314    {
315      return new ContentSyncStateControl(c.getOID(), c.isCritical(),
316           c.getValue());
317    }
318  }
319
320
321
322  /**
323   * Retrieves the synchronization state for this control, which provides
324   * information about the state of the associated search result entry or
325   * reference.
326   *
327   * @return  The state value for this content synchronization state control.
328   */
329  public ContentSyncState getState()
330  {
331    return state;
332  }
333
334
335
336  /**
337   * Retrieves the entryUUID for the associated search result entry or
338   * reference.
339   *
340   * @return  The entryUUID for the associated search result entry or
341   *          reference.
342   */
343  public UUID getEntryUUID()
344  {
345    return entryUUID;
346  }
347
348
349
350  /**
351   * Retrieves a cookie providing updated state information for the
352   * synchronization session, if available.
353   *
354   * @return  A cookie providing updated state information for the
355   *          synchronization session, or {@code null} if none was included in
356   *          the control.
357   */
358  public ASN1OctetString getCookie()
359  {
360    return cookie;
361  }
362
363
364
365  /**
366   * {@inheritDoc}
367   */
368  @Override()
369  public String getControlName()
370  {
371    return INFO_CONTROL_NAME_CONTENT_SYNC_STATE.get();
372  }
373
374
375
376  /**
377   * {@inheritDoc}
378   */
379  @Override()
380  public void toString(final StringBuilder buffer)
381  {
382    buffer.append("ContentSyncStateControl(state='");
383    buffer.append(state.name());
384    buffer.append("', entryUUID='");
385    buffer.append(entryUUID);
386    buffer.append('\'');
387
388    if (cookie != null)
389    {
390      buffer.append(", cookie=");
391      StaticUtils.toHex(cookie.getValue(), buffer);
392    }
393
394    buffer.append(')');
395  }
396}