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