001    /*
002     * Copyright 2010-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2010-2016 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     */
021    package com.unboundid.ldap.sdk.controls;
022    
023    
024    
025    import java.util.ArrayList;
026    
027    import com.unboundid.asn1.ASN1Boolean;
028    import com.unboundid.asn1.ASN1Constants;
029    import com.unboundid.asn1.ASN1Element;
030    import com.unboundid.asn1.ASN1OctetString;
031    import com.unboundid.asn1.ASN1Sequence;
032    import com.unboundid.ldap.sdk.Control;
033    import com.unboundid.ldap.sdk.DecodeableControl;
034    import com.unboundid.ldap.sdk.LDAPException;
035    import com.unboundid.ldap.sdk.LDAPResult;
036    import com.unboundid.ldap.sdk.ResultCode;
037    import com.unboundid.util.Debug;
038    import com.unboundid.util.NotMutable;
039    import com.unboundid.util.StaticUtils;
040    import com.unboundid.util.ThreadSafety;
041    import com.unboundid.util.ThreadSafetyLevel;
042    
043    import 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)
058    public 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    }