001    /*
002     * Copyright 2007-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2013 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 searchRequest =
100     *        new SearchRequest(myAsyncSearchListener, "dc=example,dc=com",
101     *                          SearchScope.SUB, "(objectClass=*)");
102     *   searchRequest.addControl(new PersistentSearchRequestControl(
103     *        PersistentSearchChangeType.allChangeTypes(), true, true));
104     *   AsyncRequestID asyncRequestID = connection.asyncSearch(searchRequest);
105     * </PRE>
106     */
107    @NotMutable()
108    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
109    public final class PersistentSearchRequestControl
110           extends Control
111    {
112      /**
113       * The OID (2.16.840.1.113730.3.4.3) for the persistent search request
114       * control.
115       */
116      public static final String PERSISTENT_SEARCH_REQUEST_OID =
117           "2.16.840.1.113730.3.4.3";
118    
119    
120    
121      /**
122       * The serial version UID for this serializable class.
123       */
124      private static final long serialVersionUID = 3532762682521779027L;
125    
126    
127    
128      // Indicates whether the search should only return search result entries for
129      // changes made to entries matching the search criteria, or if existing
130      // entries already in the server should be returned as well.
131      private final boolean changesOnly;
132    
133      // Indicates whether search result entries returned as part of this persistent
134      // search should include the entry change notification control.
135      private final boolean returnECs;
136    
137      // The set of change types for which this persistent search control is
138      // registered.
139      private final EnumSet<PersistentSearchChangeType> changeTypes;
140    
141    
142    
143      /**
144       * Creates a new persistent search control with the provided information.  It
145       * will be marked critical.
146       *
147       * @param  changeType   The change type for which to register.  It must not be
148       *                      {@code null}.
149       * @param  changesOnly  Indicates whether the search should only return search
150       *                      result entries for changes made to entries matching
151       *                      the search criteria, or if existing matching entries
152       *                      in the server should be returned as well.
153       * @param  returnECs    Indicates whether the search result entries returned
154       *                      as part of this persistent search should include the
155       *                      entry change notification control.
156       */
157      public PersistentSearchRequestControl(
158                  final PersistentSearchChangeType changeType,
159                  final boolean changesOnly, final boolean returnECs)
160      {
161        super(PERSISTENT_SEARCH_REQUEST_OID, true,
162              encodeValue(changeType, changesOnly, returnECs));
163    
164        changeTypes = EnumSet.of(changeType);
165    
166        this.changesOnly = changesOnly;
167        this.returnECs   = returnECs;
168      }
169    
170    
171    
172      /**
173       * Creates a new persistent search control with the provided information.  It
174       * will be marked critical.
175       *
176       * @param  changeTypes  The set of change types for which to register.  It
177       *                      must not be {@code null} or empty.
178       * @param  changesOnly  Indicates whether the search should only return search
179       *                      result entries for changes made to entries matching
180       *                      the search criteria, or if existing matching entries
181       *                      in the server should be returned as well.
182       * @param  returnECs    Indicates whether the search result entries returned
183       *                      as part of this persistent search should include the
184       *                      entry change notification control.
185       */
186      public PersistentSearchRequestControl(
187                  final Set<PersistentSearchChangeType> changeTypes,
188                  final boolean changesOnly, final boolean returnECs)
189      {
190        super(PERSISTENT_SEARCH_REQUEST_OID, true,
191              encodeValue(changeTypes, changesOnly, returnECs));
192    
193        this.changeTypes = EnumSet.copyOf(changeTypes);
194        this.changesOnly = changesOnly;
195        this.returnECs   = returnECs;
196      }
197    
198    
199    
200      /**
201       * Creates a new persistent search control with the provided information.
202       *
203       * @param  changeType   The change type for which to register.  It must not be
204       *                      {@code null}.
205       * @param  changesOnly  Indicates whether the search should only return search
206       *                      result entries for changes made to entries matching
207       *                      the search criteria, or if existing matching entries
208       *                      in the server should be returned as well.
209       * @param  returnECs    Indicates whether the search result entries returned
210       *                      as part of this persistent search should include the
211       *                      entry change notification control.
212       * @param  isCritical   Indicates whether the control should be marked
213       *                      critical.
214       */
215      public PersistentSearchRequestControl(
216                  final PersistentSearchChangeType changeType,
217                  final boolean changesOnly, final boolean returnECs,
218                  final boolean isCritical)
219      {
220        super(PERSISTENT_SEARCH_REQUEST_OID, isCritical,
221              encodeValue(changeType, changesOnly, returnECs));
222    
223        changeTypes = EnumSet.of(changeType);
224    
225        this.changesOnly = changesOnly;
226        this.returnECs   = returnECs;
227      }
228    
229    
230    
231      /**
232       * Creates a new persistent search control with the provided information.
233       *
234       * @param  changeTypes  The set of change types for which to register.  It
235       *                      must not be {@code null} or empty.
236       * @param  changesOnly  Indicates whether the search should only return search
237       *                      result entries for changes made to entries matching
238       *                      the search criteria, or if existing matching entries
239       *                      in the server should be returned as well.
240       * @param  returnECs    Indicates whether the search result entries returned
241       *                      as part of this persistent search should include the
242       *                      entry change notification control.
243       * @param  isCritical   Indicates whether the control should be marked
244       *                      critical.
245       */
246      public PersistentSearchRequestControl(
247                  final Set<PersistentSearchChangeType> changeTypes,
248                  final boolean changesOnly, final boolean returnECs,
249                  final boolean isCritical)
250      {
251        super(PERSISTENT_SEARCH_REQUEST_OID, isCritical,
252              encodeValue(changeTypes, changesOnly, returnECs));
253    
254        this.changeTypes = EnumSet.copyOf(changeTypes);
255        this.changesOnly = changesOnly;
256        this.returnECs   = returnECs;
257      }
258    
259    
260    
261      /**
262       * Creates a new persistent search request control which is decoded from the
263       * provided generic control.
264       *
265       * @param  control  The generic control to be decoded as a persistent search
266       *                  request control.
267       *
268       * @throws  LDAPException  If the provided control cannot be decoded as a
269       *                         persistent search request control.
270       */
271      public PersistentSearchRequestControl(final Control control)
272             throws LDAPException
273      {
274        super(control);
275    
276        final ASN1OctetString value = control.getValue();
277        if (value == null)
278        {
279          throw new LDAPException(ResultCode.DECODING_ERROR,
280                                  ERR_PSEARCH_NO_VALUE.get());
281        }
282    
283        try
284        {
285          final ASN1Element valueElement = ASN1Element.decode(value.getValue());
286          final ASN1Element[] elements =
287               ASN1Sequence.decodeAsSequence(valueElement).elements();
288    
289          changeTypes =
290               EnumSet.copyOf(PersistentSearchChangeType.decodeChangeTypes(
291                              ASN1Integer.decodeAsInteger(elements[0]).intValue()));
292          changesOnly = ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
293          returnECs   = ASN1Boolean.decodeAsBoolean(elements[2]).booleanValue();
294        }
295        catch (Exception e)
296        {
297          debugException(e);
298          throw new LDAPException(ResultCode.DECODING_ERROR,
299                                  ERR_PSEARCH_CANNOT_DECODE.get(e), e);
300        }
301      }
302    
303    
304    
305      /**
306       * Encodes the provided information into an octet string that can be used as
307       * the value for this control.
308       *
309       * @param  changeType   The change type for which to register.  It must not be
310       *                      {@code null}.
311       * @param  changesOnly  Indicates whether the search should only return search
312       *                      result entries for changes made to entries matching
313       *                      the search criteria, or if existing matching entries
314       *                      in the server should be returned as well.
315       * @param  returnECs    Indicates whether the search result entries returned
316       *                      as part of this persistent search should include the
317       *                      entry change notification control.
318       *
319       * @return  An ASN.1 octet string that can be used as the value for this
320       *          control.
321       */
322      private static ASN1OctetString encodeValue(
323                   final PersistentSearchChangeType changeType,
324                   final boolean changesOnly, final boolean returnECs)
325      {
326        ensureNotNull(changeType);
327    
328        final ASN1Element[] elements =
329        {
330          new ASN1Integer(changeType.intValue()),
331          new ASN1Boolean(changesOnly),
332          new ASN1Boolean(returnECs)
333        };
334    
335        return new ASN1OctetString(new ASN1Sequence(elements).encode());
336      }
337    
338    
339    
340      /**
341       * Encodes the provided information into an octet string that can be used as
342       * the value for this control.
343       *
344       * @param  changeTypes  The set of change types for which to register.  It
345       *                      must not be {@code null} or empty.
346       * @param  changesOnly  Indicates whether the search should only return search
347       *                      result entries for changes made to entries matching
348       *                      the search criteria, or if existing matching entries
349       *                      in the server should be returned as well.
350       * @param  returnECs    Indicates whether the search result entries returned
351       *                      as part of this persistent search should include the
352       *                      entry change notification control.
353       *
354       * @return  An ASN.1 octet string that can be used as the value for this
355       *          control.
356       */
357      private static ASN1OctetString encodeValue(
358                   final Set<PersistentSearchChangeType> changeTypes,
359                   final boolean changesOnly, final boolean returnECs)
360      {
361        ensureNotNull(changeTypes);
362        ensureFalse(changeTypes.isEmpty(),
363             "PersistentSearchRequestControl.changeTypes must not be empty.");
364    
365        final ASN1Element[] elements =
366        {
367          new ASN1Integer(
368                   PersistentSearchChangeType.encodeChangeTypes(changeTypes)),
369          new ASN1Boolean(changesOnly),
370          new ASN1Boolean(returnECs)
371        };
372    
373        return new ASN1OctetString(new ASN1Sequence(elements).encode());
374      }
375    
376    
377    
378      /**
379       * Retrieves the set of change types for this persistent search request
380       * control.
381       *
382       * @return  The set of change types for this persistent search request
383       *          control.
384       */
385      public Set<PersistentSearchChangeType> getChangeTypes()
386      {
387        return changeTypes;
388      }
389    
390    
391    
392      /**
393       * Indicates whether the search should only return search result entries for
394       * changes made to entries matching the search criteria, or if existing
395       * matching entries should be returned as well.
396       *
397       * @return  {@code true} if the search should only return search result
398       *          entries for changes matching the search criteria, or {@code false}
399       *          if it should also return existing entries that match the search
400       *          criteria.
401       */
402      public boolean changesOnly()
403      {
404        return changesOnly;
405      }
406    
407    
408    
409      /**
410       * Indicates whether the search result entries returned as part of this
411       * persistent search should include the entry change notification control.
412       *
413       * @return  {@code true} if search result entries returned as part of this
414       *          persistent search should include the entry change notification
415       *          control, or {@code false} if not.
416       */
417      public boolean returnECs()
418      {
419        return returnECs;
420      }
421    
422    
423    
424      /**
425       * {@inheritDoc}
426       */
427      @Override()
428      public String getControlName()
429      {
430        return INFO_CONTROL_NAME_PSEARCH_REQUEST.get();
431      }
432    
433    
434    
435      /**
436       * {@inheritDoc}
437       */
438      @Override()
439      public void toString(final StringBuilder buffer)
440      {
441        buffer.append("PersistentSearchRequestControl(changeTypes={");
442    
443        final Iterator<PersistentSearchChangeType> iterator =
444             changeTypes.iterator();
445        while (iterator.hasNext())
446        {
447          buffer.append(iterator.next().getName());
448          if (iterator.hasNext())
449          {
450            buffer.append(", ");
451          }
452        }
453    
454        buffer.append("}, changesOnly=");
455        buffer.append(changesOnly);
456        buffer.append(", returnECs=");
457        buffer.append(returnECs);
458        buffer.append(", isCritical=");
459        buffer.append(isCritical());
460        buffer.append(')');
461      }
462    }