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