001/*
002 * Copyright 2013-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2013-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) 2013-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.experimental;
037
038
039
040import com.unboundid.asn1.ASN1Element;
041import com.unboundid.asn1.ASN1Integer;
042import com.unboundid.asn1.ASN1OctetString;
043import com.unboundid.asn1.ASN1Sequence;
044import com.unboundid.ldap.sdk.Control;
045import com.unboundid.ldap.sdk.DecodeableControl;
046import com.unboundid.ldap.sdk.LDAPException;
047import com.unboundid.ldap.sdk.ResultCode;
048import com.unboundid.ldap.sdk.SearchResult;
049import com.unboundid.util.Debug;
050import com.unboundid.util.NotMutable;
051import com.unboundid.util.NotNull;
052import com.unboundid.util.Nullable;
053import com.unboundid.util.StaticUtils;
054import com.unboundid.util.ThreadSafety;
055import com.unboundid.util.ThreadSafetyLevel;
056
057import static com.unboundid.ldap.sdk.experimental.ExperimentalMessages.*;
058
059
060
061/**
062 * This class provides support for a control that may be used to poll an Active
063 * Directory Server for information about changes that have been processed.  Use
064 * of this control is documented at
065 * <A HREF="http://support.microsoft.com/kb/891995">
066 * http://support.microsoft.com/kb/891995</A> and at
067 * <A HREF="http://msdn.microsoft.com/en-us/library/ms677626.aspx">
068 * http://msdn.microsoft.com/en-us/library/ms677626.aspx</A>.  The control OID
069 * and value format are described at
070 * <A HREF="http://msdn.microsoft.com/en-us/library/aa366978%28VS.85%29.aspx">
071 * http://msdn.microsoft.com/en-us/library/aa366978%28VS.85%29.aspx</A> and the
072 * values of the flags are documented at
073 * <A HREF="http://msdn.microsoft.com/en-us/library/cc223347.aspx">
074 * http://msdn.microsoft.com/en-us/library/cc223347.aspx</A>.
075 * <BR><BR>
076 * <H2>Example</H2>
077 * The following example demonstrates the process for using the DirSync control
078 * to identify changes to user entries below "dc=example,dc=com":
079 * <PRE>
080 * // Create a search request that will be used to identify all users below
081 * // "dc=example,dc=com".
082 * final SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
083 *      SearchScope.SUB, Filter.createEqualityFilter("objectClass", "User"));
084 *
085 * // Define the components that will be included in the DirSync request
086 * // control.
087 * ASN1OctetString cookie = null;
088 * final int flags = ActiveDirectoryDirSyncControl.FLAG_INCREMENTAL_VALUES |
089 *      ActiveDirectoryDirSyncControl.FLAG_OBJECT_SECURITY;
090 *
091 * // Create a loop that will be used to keep polling for changes.
092 * while (keepLooping)
093 * {
094 *   // Update the controls that will be used for the search request.
095 *   searchRequest.setControls(new ActiveDirectoryDirSyncControl(true, flags,
096 *        50, cookie));
097 *
098 *   // Process the search and get the response control.
099 *   final SearchResult searchResult = connection.search(searchRequest);
100 *   ActiveDirectoryDirSyncControl dirSyncResponse =
101 *        ActiveDirectoryDirSyncControl.get(searchResult);
102 *   cookie = dirSyncResponse.getCookie();
103 *
104 *   // Process the search result entries because they represent entries that
105 *   // have been created or modified.
106 *   for (final SearchResultEntry updatedEntry :
107 *        searchResult.getSearchEntries())
108 *   {
109 *     // Do something with the entry.
110 *   }
111 *
112 *   // If the client might want to continue the search even after shutting
113 *   // down and starting back up later, then persist the cookie now.
114 * }
115 * </PRE>
116 */
117@NotMutable()
118@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
119public final class ActiveDirectoryDirSyncControl
120       extends Control
121       implements DecodeableControl
122{
123  /**
124   * The OID (1.2.840.113556.1.4.841) for the DirSync control.
125   */
126  @NotNull public static final String DIRSYNC_OID = "1.2.840.113556.1.4.841";
127
128
129
130  /**
131   * The value of the flag that indicates that the client should only be allowed
132   * to view objects and attributes that are otherwise accessible to the client.
133   */
134  public static final int FLAG_OBJECT_SECURITY = 0x0000_0001;
135
136
137
138  /**
139   * The value of the flag that indicates the server should return parent
140   * objects before child objects.
141   */
142  public static final int FLAG_ANCESTORS_FIRST_ORDER = 0x0000_0800;
143
144
145
146  /**
147   * The value of the flag that indicates that the server should not return
148   * private data in search results.
149   */
150  public static final int FLAG_PUBLIC_DATA_ONLY = 0x0000_2000;
151
152
153
154  /**
155   * The value of the flag that indicates that only changed values of attributes
156   * should be included in search results.
157   */
158  public static final int FLAG_INCREMENTAL_VALUES = 0x8000_0000;
159
160
161
162  /**
163   * The serial version UID for this serializable class.
164   */
165  private static final long serialVersionUID = -2871267685237800654L;
166
167
168
169  // A cookie that may be used to resume a previous DirSync search.
170  @Nullable private final ASN1OctetString cookie;
171
172  // The value of the flags that should be used for DirSync operation.
173  private final int flags;
174
175  // The maximum number of attributes to return.
176  private final int maxAttributeCount;
177
178
179
180  /**
181   * Creates a new empty control instance that is intended to be used only for
182   * decoding controls via the {@code DecodeableControl} interface.
183   */
184  ActiveDirectoryDirSyncControl()
185  {
186    this(true, 0, 0, null);
187  }
188
189
190
191  /**
192   * Creates a new DirSync control with the provided information.
193   *
194   * @param  isCritical         Indicates whether this control should be marked
195   *                            critical.
196   * @param  flags              The value of the flags that should be used for
197   *                            DirSync operation.  This should be zero if no
198   *                            special flags or needed, or a bitwise OR of the
199   *                            values of the individual flags that are desired.
200   * @param  maxAttributeCount  The maximum number of attributes to return.
201   * @param  cookie             A cookie that may be used to resume a previous
202   *                            DirSync search.  This may be {@code null} if
203   *                            no previous cookie is available.
204   */
205  public ActiveDirectoryDirSyncControl(final boolean isCritical,
206                                       final int flags,
207                                       final int maxAttributeCount,
208                                       @Nullable final ASN1OctetString cookie)
209  {
210    super(DIRSYNC_OID, isCritical,
211         encodeValue(flags, maxAttributeCount, cookie));
212
213    this.flags = flags;
214    this.maxAttributeCount = maxAttributeCount;
215
216    if (cookie == null)
217    {
218      this.cookie = new ASN1OctetString();
219    }
220    else
221    {
222      this.cookie = cookie;
223    }
224  }
225
226
227
228  /**
229   * Creates a new DirSync control with settings decoded from the provided
230   * control information.
231   *
232   * @param  oid         The OID of the control to be decoded.
233   * @param  isCritical  The criticality of the control to be decoded.
234   * @param  value       The value of the control to be decoded.
235   *
236   * @throws LDAPException  If a problem is encountered while attempting to
237   *                         decode the control value as appropriate for a
238   *                         DirSync control.
239   */
240  public ActiveDirectoryDirSyncControl(@NotNull final String oid,
241                                       final boolean isCritical,
242                                       @Nullable final ASN1OctetString value)
243       throws LDAPException
244  {
245    super(oid, isCritical, value);
246
247    if (value == null)
248    {
249      throw new LDAPException(ResultCode.DECODING_ERROR,
250           ERR_DIRSYNC_CONTROL_NO_VALUE.get());
251    }
252
253    try
254    {
255      final ASN1Element[] elements =
256           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
257      flags = ASN1Integer.decodeAsInteger(elements[0]).intValue();
258      maxAttributeCount = ASN1Integer.decodeAsInteger(elements[1]).intValue();
259      cookie = ASN1OctetString.decodeAsOctetString(elements[2]);
260    }
261    catch (final Exception e)
262    {
263      Debug.debugException(e);
264
265      throw new LDAPException(ResultCode.DECODING_ERROR,
266           ERR_DIRSYNC_CONTROL_DECODE_ERROR.get(
267                StaticUtils.getExceptionMessage(e)),
268           e);
269    }
270  }
271
272
273
274  /**
275   * Encodes the provided information into a format appropriate for use as the
276   * value of a DirSync control.
277   *
278   * @param  flags              The value of the flags that should be used for
279   *                            DirSync operation.  This should be zero if no
280   *                            special flags or needed, or a bitwise OR of the
281   *                            values of the individual flags that are desired.
282   * @param  maxAttributeCount  The maximum number of attributes to return.
283   * @param  cookie             A cookie that may be used to resume a previous
284   *                            DirSync search.  This may be {@code null} if
285   *                            no previous cookie is available.
286   *
287   * @return  An ASN.1 octet string containing the encoded control value.
288   */
289  @NotNull()
290  private static ASN1OctetString encodeValue(final int flags,
291                                      final int maxAttributeCount,
292                                      @Nullable final ASN1OctetString cookie)
293  {
294    final ASN1Element[] valueElements = new ASN1Element[3];
295    valueElements[0] = new ASN1Integer(flags);
296    valueElements[1] = new ASN1Integer(maxAttributeCount);
297
298    if (cookie == null)
299    {
300      valueElements[2] = new ASN1OctetString();
301    }
302    else
303    {
304      valueElements[2] = cookie;
305    }
306
307    return new ASN1OctetString(new ASN1Sequence(valueElements).encode());
308  }
309
310
311
312  /**
313   * {@inheritDoc}
314   */
315  @Override()
316  @NotNull()
317  public ActiveDirectoryDirSyncControl decodeControl(@NotNull final String oid,
318              final boolean isCritical, @Nullable final ASN1OctetString value)
319          throws LDAPException
320  {
321    return new ActiveDirectoryDirSyncControl(oid, isCritical, value);
322  }
323
324
325
326  /**
327   * Retrieves the value of the flags that should be used for DirSync operation.
328   *
329   * @return  The value of the flags that should be used for DirSync operation.
330   */
331  public int getFlags()
332  {
333    return flags;
334  }
335
336
337
338  /**
339   * Retrieves the maximum number of attributes to return.
340   *
341   * @return  The maximum number of attributes to return.
342   */
343  public int getMaxAttributeCount()
344  {
345    return maxAttributeCount;
346  }
347
348
349
350  /**
351   * Retrieves a cookie that may be used to resume a previous DirSync search,
352   * if available.
353   *
354   * @return  A cookie that may be used to resume a previous DirSync search, or
355   *          a zero-length cookie if there is none.
356   */
357  @Nullable()
358  public ASN1OctetString getCookie()
359  {
360    return cookie;
361  }
362
363
364
365  /**
366   * Extracts a DirSync response control from the provided result.
367   *
368   * @param  result  The result from which to retrieve the DirSync response
369   *                 control.
370   *
371   * @return  The DirSync response control contained in the provided result, or
372   *          {@code null} if the result did not include a DirSync response
373   *          control.
374   *
375   * @throws  LDAPException  If a problem is encountered while attempting to
376   *                         decode the DirSync response control contained in
377   *                         the provided result.
378   */
379  @Nullable()
380  public static ActiveDirectoryDirSyncControl get(
381                     @NotNull final SearchResult result)
382         throws LDAPException
383  {
384    final Control c = result.getResponseControl(DIRSYNC_OID);
385    if (c == null)
386    {
387      return null;
388    }
389
390    if (c instanceof ActiveDirectoryDirSyncControl)
391    {
392      return (ActiveDirectoryDirSyncControl) c;
393    }
394    else
395    {
396      return new ActiveDirectoryDirSyncControl(c.getOID(), c.isCritical(),
397           c.getValue());
398    }
399  }
400
401
402
403  /**
404   * {@inheritDoc}
405   */
406  @Override()
407  @NotNull()
408  public String getControlName()
409  {
410    return INFO_CONTROL_NAME_DIRSYNC.get();
411  }
412
413
414
415  /**
416   * {@inheritDoc}
417   */
418  @Override()
419  public void toString(@NotNull final StringBuilder buffer)
420  {
421    buffer.append("ActiveDirectoryDirSyncControl(isCritical=");
422    buffer.append(isCritical());
423    buffer.append(", flags=");
424    buffer.append(flags);
425    buffer.append(", maxAttributeCount=");
426    buffer.append(maxAttributeCount);
427    buffer.append(", cookie=byte[");
428    buffer.append(cookie.getValueLength());
429    buffer.append("])");
430  }
431}