001    /*
002     * Copyright 2007-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-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.EnumSet;
026    import java.util.Iterator;
027    import java.util.Set;
028    
029    import com.unboundid.asn1.ASN1Boolean;
030    import com.unboundid.asn1.ASN1Element;
031    import com.unboundid.asn1.ASN1Integer;
032    import com.unboundid.asn1.ASN1OctetString;
033    import com.unboundid.asn1.ASN1Sequence;
034    import com.unboundid.ldap.sdk.Control;
035    import com.unboundid.ldap.sdk.LDAPException;
036    import com.unboundid.ldap.sdk.ResultCode;
037    import com.unboundid.util.NotMutable;
038    import com.unboundid.util.ThreadSafety;
039    import com.unboundid.util.ThreadSafetyLevel;
040    
041    import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
042    import static com.unboundid.util.Debug.*;
043    import 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)
126    public 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    }