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