001 /*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2014 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 }