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