001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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.EnumSet;
026import java.util.Iterator;
027import java.util.Set;
028
029import com.unboundid.asn1.ASN1Boolean;
030import com.unboundid.asn1.ASN1Element;
031import com.unboundid.asn1.ASN1Integer;
032import com.unboundid.asn1.ASN1OctetString;
033import com.unboundid.asn1.ASN1Sequence;
034import com.unboundid.ldap.sdk.Control;
035import com.unboundid.ldap.sdk.LDAPException;
036import com.unboundid.ldap.sdk.ResultCode;
037import com.unboundid.util.NotMutable;
038import com.unboundid.util.ThreadSafety;
039import com.unboundid.util.ThreadSafetyLevel;
040
041import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
042import static com.unboundid.util.Debug.*;
043import static com.unboundid.util.Validator.*;
044
045
046
047/**
048 * This class provides an implementation of the persistent search request
049 * control as defined in draft-ietf-ldapext-psearch.  It may be included in a
050 * search request to request notification for changes to entries that match the
051 * associated set of search criteria.  It can provide a basic mechanism for
052 * clients to request to be notified whenever entries matching the associated
053 * search criteria are altered.
054 * <BR><BR>
055 * A persistent search request control may include the following elements:
056 * <UL>
057 *   <LI>{@code changeTypes} -- Specifies the set of change types for which to
058 *       receive notification.  This may be any combination of one or more of
059 *       the {@link PersistentSearchChangeType} values.</LI>
060 *   <LI>{@code changesOnly} -- Indicates whether to only return updated entries
061 *       that match the associated search criteria.  If this is {@code false},
062 *       then the server will first return all existing entries in the server
063 *       that match the search criteria, and will then begin returning entries
064 *       that are updated in an operation associated with one of the
065 *       registered {@code changeTypes}.  If this is {@code true}, then the
066 *       server will not return all matching entries that already exist in the
067 *       server but will only return entries in response to changes that
068 *       occur.</LI>
069 *   <LI>{@code returnECs} -- Indicates whether search result entries returned
070 *       as a result of a change to the directory data should include the
071 *       {@link EntryChangeNotificationControl} to provide information about
072 *       the type of operation that occurred.  If {@code changesOnly} is
073 *       {@code false}, then entry change notification controls will not be
074 *       included in existing entries that match the search criteria, but only
075 *       in entries that are updated by an operation with one of the registered
076 *       {@code changeTypes}.</LI>
077 * </UL>
078 * Note that when an entry is returned in response to a persistent search
079 * request, the content of the entry that is returned will reflect the updated
080 * entry in the server (except in the case of a delete operation, in which case
081 * it will be the entry as it appeared before it was removed).  Other than the
082 * information included in the entry change notification control, the search
083 * result entry will not contain any information about what actually changed in
084 * the entry.
085 * <BR><BR>
086 * Many servers do not enforce time limit or size limit restrictions on the
087 * persistent search control, and because there is no defined "end" to the
088 * search, it may remain active until the client abandons or cancels the search
089 * or until the connection is closed.  Because of this, it is strongly
090 * recommended that clients only use the persistent search request control in
091 * conjunction with asynchronous search operations invoked using the
092 * {@link com.unboundid.ldap.sdk.LDAPConnection#asyncSearch} method.
093 * <BR><BR>
094 * <H2>Example</H2>
095 * The following example demonstrates the process for beginning an asynchronous
096 * search that includes the persistent search control in order to notify the
097 * client of all changes to entries within the "dc=example,dc=com" subtree.
098 * <PRE>
099 * SearchRequest persistentSearchRequest = new SearchRequest(
100 *      asyncSearchListener, "dc=example,dc=com", SearchScope.SUB,
101 *      Filter.createPresenceFilter("objectClass"));
102 * persistentSearchRequest.addControl(new PersistentSearchRequestControl(
103 *      PersistentSearchChangeType.allChangeTypes(), // Notify change types.
104 *      true, // Only return new changes, don't match existing entries.
105 *      true)); // Include change notification controls in search entries.
106 *
107 * // Launch the persistent search as an asynchronous operation.
108 * AsyncRequestID persistentSearchRequestID =
109 *      connection.asyncSearch(persistentSearchRequest);
110 *
111 * // Modify an entry that matches the persistent search criteria.  This
112 * // should cause the persistent search listener to be notified.
113 * LDAPResult modifyResult = connection.modify(
114 *      "uid=test.user,ou=People,dc=example,dc=com",
115 *      new Modification(ModificationType.REPLACE, "description", "test"));
116 *
117 * // Verify that the persistent search listener was notified....
118 *
119 * // Since persistent search operations don't end on their own, we need to
120 * // abandon the search when we don't need it anymore.
121 * connection.abandon(persistentSearchRequestID);
122 * </PRE>
123 */
124@NotMutable()
125@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
126public final class PersistentSearchRequestControl
127       extends Control
128{
129  /**
130   * The OID (2.16.840.1.113730.3.4.3) for the persistent search request
131   * control.
132   */
133  public static final String PERSISTENT_SEARCH_REQUEST_OID =
134       "2.16.840.1.113730.3.4.3";
135
136
137
138  /**
139   * The serial version UID for this serializable class.
140   */
141  private static final long serialVersionUID = 3532762682521779027L;
142
143
144
145  // Indicates whether the search should only return search result entries for
146  // changes made to entries matching the search criteria, or if existing
147  // entries already in the server should be returned as well.
148  private final boolean changesOnly;
149
150  // Indicates whether search result entries returned as part of this persistent
151  // search should include the entry change notification control.
152  private final boolean returnECs;
153
154  // The set of change types for which this persistent search control is
155  // registered.
156  private final EnumSet<PersistentSearchChangeType> changeTypes;
157
158
159
160  /**
161   * Creates a new persistent search control with the provided information.  It
162   * will be marked critical.
163   *
164   * @param  changeType   The change type for which to register.  It must not be
165   *                      {@code null}.
166   * @param  changesOnly  Indicates whether the search should only return search
167   *                      result entries for changes made to entries matching
168   *                      the search criteria, or if existing matching entries
169   *                      in the server should be returned as well.
170   * @param  returnECs    Indicates whether the search result entries returned
171   *                      as part of this persistent search should include the
172   *                      entry change notification control.
173   */
174  public PersistentSearchRequestControl(
175              final PersistentSearchChangeType changeType,
176              final boolean changesOnly, final boolean returnECs)
177  {
178    super(PERSISTENT_SEARCH_REQUEST_OID, true,
179          encodeValue(changeType, changesOnly, returnECs));
180
181    changeTypes = EnumSet.of(changeType);
182
183    this.changesOnly = changesOnly;
184    this.returnECs   = returnECs;
185  }
186
187
188
189  /**
190   * Creates a new persistent search control with the provided information.  It
191   * will be marked critical.
192   *
193   * @param  changeTypes  The set of change types for which to register.  It
194   *                      must not be {@code null} or empty.
195   * @param  changesOnly  Indicates whether the search should only return search
196   *                      result entries for changes made to entries matching
197   *                      the search criteria, or if existing matching entries
198   *                      in the server should be returned as well.
199   * @param  returnECs    Indicates whether the search result entries returned
200   *                      as part of this persistent search should include the
201   *                      entry change notification control.
202   */
203  public PersistentSearchRequestControl(
204              final Set<PersistentSearchChangeType> changeTypes,
205              final boolean changesOnly, final boolean returnECs)
206  {
207    super(PERSISTENT_SEARCH_REQUEST_OID, true,
208          encodeValue(changeTypes, changesOnly, returnECs));
209
210    this.changeTypes = EnumSet.copyOf(changeTypes);
211    this.changesOnly = changesOnly;
212    this.returnECs   = returnECs;
213  }
214
215
216
217  /**
218   * Creates a new persistent search control with the provided information.
219   *
220   * @param  changeType   The change type for which to register.  It must not be
221   *                      {@code null}.
222   * @param  changesOnly  Indicates whether the search should only return search
223   *                      result entries for changes made to entries matching
224   *                      the search criteria, or if existing matching entries
225   *                      in the server should be returned as well.
226   * @param  returnECs    Indicates whether the search result entries returned
227   *                      as part of this persistent search should include the
228   *                      entry change notification control.
229   * @param  isCritical   Indicates whether the control should be marked
230   *                      critical.
231   */
232  public PersistentSearchRequestControl(
233              final PersistentSearchChangeType changeType,
234              final boolean changesOnly, final boolean returnECs,
235              final boolean isCritical)
236  {
237    super(PERSISTENT_SEARCH_REQUEST_OID, isCritical,
238          encodeValue(changeType, changesOnly, returnECs));
239
240    changeTypes = EnumSet.of(changeType);
241
242    this.changesOnly = changesOnly;
243    this.returnECs   = returnECs;
244  }
245
246
247
248  /**
249   * Creates a new persistent search control with the provided information.
250   *
251   * @param  changeTypes  The set of change types for which to register.  It
252   *                      must not be {@code null} or empty.
253   * @param  changesOnly  Indicates whether the search should only return search
254   *                      result entries for changes made to entries matching
255   *                      the search criteria, or if existing matching entries
256   *                      in the server should be returned as well.
257   * @param  returnECs    Indicates whether the search result entries returned
258   *                      as part of this persistent search should include the
259   *                      entry change notification control.
260   * @param  isCritical   Indicates whether the control should be marked
261   *                      critical.
262   */
263  public PersistentSearchRequestControl(
264              final Set<PersistentSearchChangeType> changeTypes,
265              final boolean changesOnly, final boolean returnECs,
266              final boolean isCritical)
267  {
268    super(PERSISTENT_SEARCH_REQUEST_OID, isCritical,
269          encodeValue(changeTypes, changesOnly, returnECs));
270
271    this.changeTypes = EnumSet.copyOf(changeTypes);
272    this.changesOnly = changesOnly;
273    this.returnECs   = returnECs;
274  }
275
276
277
278  /**
279   * Creates a new persistent search request control which is decoded from the
280   * provided generic control.
281   *
282   * @param  control  The generic control to be decoded as a persistent search
283   *                  request control.
284   *
285   * @throws  LDAPException  If the provided control cannot be decoded as a
286   *                         persistent search request control.
287   */
288  public PersistentSearchRequestControl(final Control control)
289         throws LDAPException
290  {
291    super(control);
292
293    final ASN1OctetString value = control.getValue();
294    if (value == null)
295    {
296      throw new LDAPException(ResultCode.DECODING_ERROR,
297                              ERR_PSEARCH_NO_VALUE.get());
298    }
299
300    try
301    {
302      final ASN1Element valueElement = ASN1Element.decode(value.getValue());
303      final ASN1Element[] elements =
304           ASN1Sequence.decodeAsSequence(valueElement).elements();
305
306      changeTypes =
307           EnumSet.copyOf(PersistentSearchChangeType.decodeChangeTypes(
308                          ASN1Integer.decodeAsInteger(elements[0]).intValue()));
309      changesOnly = ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
310      returnECs   = ASN1Boolean.decodeAsBoolean(elements[2]).booleanValue();
311    }
312    catch (Exception e)
313    {
314      debugException(e);
315      throw new LDAPException(ResultCode.DECODING_ERROR,
316                              ERR_PSEARCH_CANNOT_DECODE.get(e), e);
317    }
318  }
319
320
321
322  /**
323   * Encodes the provided information into an octet string that can be used as
324   * the value for this control.
325   *
326   * @param  changeType   The change type for which to register.  It must not be
327   *                      {@code null}.
328   * @param  changesOnly  Indicates whether the search should only return search
329   *                      result entries for changes made to entries matching
330   *                      the search criteria, or if existing matching entries
331   *                      in the server should be returned as well.
332   * @param  returnECs    Indicates whether the search result entries returned
333   *                      as part of this persistent search should include the
334   *                      entry change notification control.
335   *
336   * @return  An ASN.1 octet string that can be used as the value for this
337   *          control.
338   */
339  private static ASN1OctetString encodeValue(
340               final PersistentSearchChangeType changeType,
341               final boolean changesOnly, final boolean returnECs)
342  {
343    ensureNotNull(changeType);
344
345    final ASN1Element[] elements =
346    {
347      new ASN1Integer(changeType.intValue()),
348      new ASN1Boolean(changesOnly),
349      new ASN1Boolean(returnECs)
350    };
351
352    return new ASN1OctetString(new ASN1Sequence(elements).encode());
353  }
354
355
356
357  /**
358   * Encodes the provided information into an octet string that can be used as
359   * the value for this control.
360   *
361   * @param  changeTypes  The set of change types for which to register.  It
362   *                      must not be {@code null} or empty.
363   * @param  changesOnly  Indicates whether the search should only return search
364   *                      result entries for changes made to entries matching
365   *                      the search criteria, or if existing matching entries
366   *                      in the server should be returned as well.
367   * @param  returnECs    Indicates whether the search result entries returned
368   *                      as part of this persistent search should include the
369   *                      entry change notification control.
370   *
371   * @return  An ASN.1 octet string that can be used as the value for this
372   *          control.
373   */
374  private static ASN1OctetString encodeValue(
375               final Set<PersistentSearchChangeType> changeTypes,
376               final boolean changesOnly, final boolean returnECs)
377  {
378    ensureNotNull(changeTypes);
379    ensureFalse(changeTypes.isEmpty(),
380         "PersistentSearchRequestControl.changeTypes must not be empty.");
381
382    final ASN1Element[] elements =
383    {
384      new ASN1Integer(
385               PersistentSearchChangeType.encodeChangeTypes(changeTypes)),
386      new ASN1Boolean(changesOnly),
387      new ASN1Boolean(returnECs)
388    };
389
390    return new ASN1OctetString(new ASN1Sequence(elements).encode());
391  }
392
393
394
395  /**
396   * Retrieves the set of change types for this persistent search request
397   * control.
398   *
399   * @return  The set of change types for this persistent search request
400   *          control.
401   */
402  public Set<PersistentSearchChangeType> getChangeTypes()
403  {
404    return changeTypes;
405  }
406
407
408
409  /**
410   * Indicates whether the search should only return search result entries for
411   * changes made to entries matching the search criteria, or if existing
412   * matching entries should be returned as well.
413   *
414   * @return  {@code true} if the search should only return search result
415   *          entries for changes matching the search criteria, or {@code false}
416   *          if it should also return existing entries that match the search
417   *          criteria.
418   */
419  public boolean changesOnly()
420  {
421    return changesOnly;
422  }
423
424
425
426  /**
427   * Indicates whether the search result entries returned as part of this
428   * persistent search should include the entry change notification control.
429   *
430   * @return  {@code true} if search result entries returned as part of this
431   *          persistent search should include the entry change notification
432   *          control, or {@code false} if not.
433   */
434  public boolean returnECs()
435  {
436    return returnECs;
437  }
438
439
440
441  /**
442   * {@inheritDoc}
443   */
444  @Override()
445  public String getControlName()
446  {
447    return INFO_CONTROL_NAME_PSEARCH_REQUEST.get();
448  }
449
450
451
452  /**
453   * {@inheritDoc}
454   */
455  @Override()
456  public void toString(final StringBuilder buffer)
457  {
458    buffer.append("PersistentSearchRequestControl(changeTypes={");
459
460    final Iterator<PersistentSearchChangeType> iterator =
461         changeTypes.iterator();
462    while (iterator.hasNext())
463    {
464      buffer.append(iterator.next().getName());
465      if (iterator.hasNext())
466      {
467        buffer.append(", ");
468      }
469    }
470
471    buffer.append("}, changesOnly=");
472    buffer.append(changesOnly);
473    buffer.append(", returnECs=");
474    buffer.append(returnECs);
475    buffer.append(", isCritical=");
476    buffer.append(isCritical());
477    buffer.append(')');
478  }
479}