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 }