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