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