001/* 002 * Copyright 2007-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2008-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.controls; 037 038 039 040import java.util.EnumSet; 041import java.util.Iterator; 042import java.util.Set; 043 044import com.unboundid.asn1.ASN1Boolean; 045import com.unboundid.asn1.ASN1Element; 046import com.unboundid.asn1.ASN1Integer; 047import com.unboundid.asn1.ASN1OctetString; 048import com.unboundid.asn1.ASN1Sequence; 049import com.unboundid.ldap.sdk.Control; 050import com.unboundid.ldap.sdk.LDAPException; 051import com.unboundid.ldap.sdk.ResultCode; 052import com.unboundid.util.Debug; 053import com.unboundid.util.NotMutable; 054import com.unboundid.util.ThreadSafety; 055import com.unboundid.util.ThreadSafetyLevel; 056import com.unboundid.util.Validator; 057 058import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 059 060 061 062/** 063 * This class provides an implementation of the persistent search request 064 * control as defined in draft-ietf-ldapext-psearch. It may be included in a 065 * search request to request notification for changes to entries that match the 066 * associated set of search criteria. It can provide a basic mechanism for 067 * clients to request to be notified whenever entries matching the associated 068 * search criteria are altered. 069 * <BR><BR> 070 * A persistent search request control may include the following elements: 071 * <UL> 072 * <LI>{@code changeTypes} -- Specifies the set of change types for which to 073 * receive notification. This may be any combination of one or more of 074 * the {@link PersistentSearchChangeType} values.</LI> 075 * <LI>{@code changesOnly} -- Indicates whether to only return updated entries 076 * that match the associated search criteria. If this is {@code false}, 077 * then the server will first return all existing entries in the server 078 * that match the search criteria, and will then begin returning entries 079 * that are updated in an operation associated with one of the 080 * registered {@code changeTypes}. If this is {@code true}, then the 081 * server will not return all matching entries that already exist in the 082 * server but will only return entries in response to changes that 083 * occur.</LI> 084 * <LI>{@code returnECs} -- Indicates whether search result entries returned 085 * as a result of a change to the directory data should include the 086 * {@link EntryChangeNotificationControl} to provide information about 087 * the type of operation that occurred. If {@code changesOnly} is 088 * {@code false}, then entry change notification controls will not be 089 * included in existing entries that match the search criteria, but only 090 * in entries that are updated by an operation with one of the registered 091 * {@code changeTypes}.</LI> 092 * </UL> 093 * Note that when an entry is returned in response to a persistent search 094 * request, the content of the entry that is returned will reflect the updated 095 * entry in the server (except in the case of a delete operation, in which case 096 * it will be the entry as it appeared before it was removed). Other than the 097 * information included in the entry change notification control, the search 098 * result entry will not contain any information about what actually changed in 099 * the entry. 100 * <BR><BR> 101 * Many servers do not enforce time limit or size limit restrictions on the 102 * persistent search control, and because there is no defined "end" to the 103 * search, it may remain active until the client abandons or cancels the search 104 * or until the connection is closed. Because of this, it is strongly 105 * recommended that clients only use the persistent search request control in 106 * conjunction with asynchronous search operations invoked using the 107 * {@link com.unboundid.ldap.sdk.LDAPConnection#asyncSearch} method. 108 * <BR><BR> 109 * <H2>Example</H2> 110 * The following example demonstrates the process for beginning an asynchronous 111 * search that includes the persistent search control in order to notify the 112 * client of all changes to entries within the "dc=example,dc=com" subtree. 113 * <PRE> 114 * SearchRequest persistentSearchRequest = new SearchRequest( 115 * asyncSearchListener, "dc=example,dc=com", SearchScope.SUB, 116 * Filter.createPresenceFilter("objectClass")); 117 * persistentSearchRequest.addControl(new PersistentSearchRequestControl( 118 * PersistentSearchChangeType.allChangeTypes(), // Notify change types. 119 * true, // Only return new changes, don't match existing entries. 120 * true)); // Include change notification controls in search entries. 121 * 122 * // Launch the persistent search as an asynchronous operation. 123 * AsyncRequestID persistentSearchRequestID = 124 * connection.asyncSearch(persistentSearchRequest); 125 * 126 * // Modify an entry that matches the persistent search criteria. This 127 * // should cause the persistent search listener to be notified. 128 * LDAPResult modifyResult = connection.modify( 129 * "uid=test.user,ou=People,dc=example,dc=com", 130 * new Modification(ModificationType.REPLACE, "description", "test")); 131 * 132 * // Verify that the persistent search listener was notified.... 133 * 134 * // Since persistent search operations don't end on their own, we need to 135 * // abandon the search when we don't need it anymore. 136 * connection.abandon(persistentSearchRequestID); 137 * </PRE> 138 */ 139@NotMutable() 140@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 141public final class PersistentSearchRequestControl 142 extends Control 143{ 144 /** 145 * The OID (2.16.840.1.113730.3.4.3) for the persistent search request 146 * control. 147 */ 148 public static final String PERSISTENT_SEARCH_REQUEST_OID = 149 "2.16.840.1.113730.3.4.3"; 150 151 152 153 /** 154 * The serial version UID for this serializable class. 155 */ 156 private static final long serialVersionUID = 3532762682521779027L; 157 158 159 160 // Indicates whether the search should only return search result entries for 161 // changes made to entries matching the search criteria, or if existing 162 // entries already in the server should be returned as well. 163 private final boolean changesOnly; 164 165 // Indicates whether search result entries returned as part of this persistent 166 // search should include the entry change notification control. 167 private final boolean returnECs; 168 169 // The set of change types for which this persistent search control is 170 // registered. 171 private final EnumSet<PersistentSearchChangeType> changeTypes; 172 173 174 175 /** 176 * Creates a new persistent search control with the provided information. It 177 * will be marked critical. 178 * 179 * @param changeType The change type for which to register. It must not be 180 * {@code null}. 181 * @param changesOnly Indicates whether the search should only return search 182 * result entries for changes made to entries matching 183 * the search criteria, or if existing matching entries 184 * in the server should be returned as well. 185 * @param returnECs Indicates whether the search result entries returned 186 * as part of this persistent search should include the 187 * entry change notification control. 188 */ 189 public PersistentSearchRequestControl( 190 final PersistentSearchChangeType changeType, 191 final boolean changesOnly, final boolean returnECs) 192 { 193 super(PERSISTENT_SEARCH_REQUEST_OID, true, 194 encodeValue(changeType, changesOnly, returnECs)); 195 196 changeTypes = EnumSet.of(changeType); 197 198 this.changesOnly = changesOnly; 199 this.returnECs = returnECs; 200 } 201 202 203 204 /** 205 * Creates a new persistent search control with the provided information. It 206 * will be marked critical. 207 * 208 * @param changeTypes The set of change types for which to register. It 209 * must not be {@code null} or empty. 210 * @param changesOnly Indicates whether the search should only return search 211 * result entries for changes made to entries matching 212 * the search criteria, or if existing matching entries 213 * in the server should be returned as well. 214 * @param returnECs Indicates whether the search result entries returned 215 * as part of this persistent search should include the 216 * entry change notification control. 217 */ 218 public PersistentSearchRequestControl( 219 final Set<PersistentSearchChangeType> changeTypes, 220 final boolean changesOnly, final boolean returnECs) 221 { 222 super(PERSISTENT_SEARCH_REQUEST_OID, true, 223 encodeValue(changeTypes, changesOnly, returnECs)); 224 225 this.changeTypes = EnumSet.copyOf(changeTypes); 226 this.changesOnly = changesOnly; 227 this.returnECs = returnECs; 228 } 229 230 231 232 /** 233 * Creates a new persistent search control with the provided information. 234 * 235 * @param changeType The change type for which to register. It must not be 236 * {@code null}. 237 * @param changesOnly Indicates whether the search should only return search 238 * result entries for changes made to entries matching 239 * the search criteria, or if existing matching entries 240 * in the server should be returned as well. 241 * @param returnECs Indicates whether the search result entries returned 242 * as part of this persistent search should include the 243 * entry change notification control. 244 * @param isCritical Indicates whether the control should be marked 245 * critical. 246 */ 247 public PersistentSearchRequestControl( 248 final PersistentSearchChangeType changeType, 249 final boolean changesOnly, final boolean returnECs, 250 final boolean isCritical) 251 { 252 super(PERSISTENT_SEARCH_REQUEST_OID, isCritical, 253 encodeValue(changeType, changesOnly, returnECs)); 254 255 changeTypes = EnumSet.of(changeType); 256 257 this.changesOnly = changesOnly; 258 this.returnECs = returnECs; 259 } 260 261 262 263 /** 264 * Creates a new persistent search control with the provided information. 265 * 266 * @param changeTypes The set of change types for which to register. It 267 * must not be {@code null} or empty. 268 * @param changesOnly Indicates whether the search should only return search 269 * result entries for changes made to entries matching 270 * the search criteria, or if existing matching entries 271 * in the server should be returned as well. 272 * @param returnECs Indicates whether the search result entries returned 273 * as part of this persistent search should include the 274 * entry change notification control. 275 * @param isCritical Indicates whether the control should be marked 276 * critical. 277 */ 278 public PersistentSearchRequestControl( 279 final Set<PersistentSearchChangeType> changeTypes, 280 final boolean changesOnly, final boolean returnECs, 281 final boolean isCritical) 282 { 283 super(PERSISTENT_SEARCH_REQUEST_OID, isCritical, 284 encodeValue(changeTypes, changesOnly, returnECs)); 285 286 this.changeTypes = EnumSet.copyOf(changeTypes); 287 this.changesOnly = changesOnly; 288 this.returnECs = returnECs; 289 } 290 291 292 293 /** 294 * Creates a new persistent search request control which is decoded from the 295 * provided generic control. 296 * 297 * @param control The generic control to be decoded as a persistent search 298 * request control. 299 * 300 * @throws LDAPException If the provided control cannot be decoded as a 301 * persistent search request control. 302 */ 303 public PersistentSearchRequestControl(final Control control) 304 throws LDAPException 305 { 306 super(control); 307 308 final ASN1OctetString value = control.getValue(); 309 if (value == null) 310 { 311 throw new LDAPException(ResultCode.DECODING_ERROR, 312 ERR_PSEARCH_NO_VALUE.get()); 313 } 314 315 try 316 { 317 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 318 final ASN1Element[] elements = 319 ASN1Sequence.decodeAsSequence(valueElement).elements(); 320 321 changeTypes = 322 EnumSet.copyOf(PersistentSearchChangeType.decodeChangeTypes( 323 ASN1Integer.decodeAsInteger(elements[0]).intValue())); 324 changesOnly = ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue(); 325 returnECs = ASN1Boolean.decodeAsBoolean(elements[2]).booleanValue(); 326 } 327 catch (final Exception e) 328 { 329 Debug.debugException(e); 330 throw new LDAPException(ResultCode.DECODING_ERROR, 331 ERR_PSEARCH_CANNOT_DECODE.get(e), e); 332 } 333 } 334 335 336 337 /** 338 * Encodes the provided information into an octet string that can be used as 339 * the value for this control. 340 * 341 * @param changeType The change type for which to register. It must not be 342 * {@code null}. 343 * @param changesOnly Indicates whether the search should only return search 344 * result entries for changes made to entries matching 345 * the search criteria, or if existing matching entries 346 * in the server should be returned as well. 347 * @param returnECs Indicates whether the search result entries returned 348 * as part of this persistent search should include the 349 * entry change notification control. 350 * 351 * @return An ASN.1 octet string that can be used as the value for this 352 * control. 353 */ 354 private static ASN1OctetString encodeValue( 355 final PersistentSearchChangeType changeType, 356 final boolean changesOnly, final boolean returnECs) 357 { 358 Validator.ensureNotNull(changeType); 359 360 final ASN1Element[] elements = 361 { 362 new ASN1Integer(changeType.intValue()), 363 new ASN1Boolean(changesOnly), 364 new ASN1Boolean(returnECs) 365 }; 366 367 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 368 } 369 370 371 372 /** 373 * Encodes the provided information into an octet string that can be used as 374 * the value for this control. 375 * 376 * @param changeTypes The set of change types for which to register. It 377 * must not be {@code null} or empty. 378 * @param changesOnly Indicates whether the search should only return search 379 * result entries for changes made to entries matching 380 * the search criteria, or if existing matching entries 381 * in the server should be returned as well. 382 * @param returnECs Indicates whether the search result entries returned 383 * as part of this persistent search should include the 384 * entry change notification control. 385 * 386 * @return An ASN.1 octet string that can be used as the value for this 387 * control. 388 */ 389 private static ASN1OctetString encodeValue( 390 final Set<PersistentSearchChangeType> changeTypes, 391 final boolean changesOnly, final boolean returnECs) 392 { 393 Validator.ensureNotNull(changeTypes); 394 Validator.ensureFalse(changeTypes.isEmpty(), 395 "PersistentSearchRequestControl.changeTypes must not be empty."); 396 397 final ASN1Element[] elements = 398 { 399 new ASN1Integer( 400 PersistentSearchChangeType.encodeChangeTypes(changeTypes)), 401 new ASN1Boolean(changesOnly), 402 new ASN1Boolean(returnECs) 403 }; 404 405 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 406 } 407 408 409 410 /** 411 * Retrieves the set of change types for this persistent search request 412 * control. 413 * 414 * @return The set of change types for this persistent search request 415 * control. 416 */ 417 public Set<PersistentSearchChangeType> getChangeTypes() 418 { 419 return changeTypes; 420 } 421 422 423 424 /** 425 * Indicates whether the search should only return search result entries for 426 * changes made to entries matching the search criteria, or if existing 427 * matching entries should be returned as well. 428 * 429 * @return {@code true} if the search should only return search result 430 * entries for changes matching the search criteria, or {@code false} 431 * if it should also return existing entries that match the search 432 * criteria. 433 */ 434 public boolean changesOnly() 435 { 436 return changesOnly; 437 } 438 439 440 441 /** 442 * Indicates whether the search result entries returned as part of this 443 * persistent search should include the entry change notification control. 444 * 445 * @return {@code true} if search result entries returned as part of this 446 * persistent search should include the entry change notification 447 * control, or {@code false} if not. 448 */ 449 public boolean returnECs() 450 { 451 return returnECs; 452 } 453 454 455 456 /** 457 * {@inheritDoc} 458 */ 459 @Override() 460 public String getControlName() 461 { 462 return INFO_CONTROL_NAME_PSEARCH_REQUEST.get(); 463 } 464 465 466 467 /** 468 * {@inheritDoc} 469 */ 470 @Override() 471 public void toString(final StringBuilder buffer) 472 { 473 buffer.append("PersistentSearchRequestControl(changeTypes={"); 474 475 final Iterator<PersistentSearchChangeType> iterator = 476 changeTypes.iterator(); 477 while (iterator.hasNext()) 478 { 479 buffer.append(iterator.next().getName()); 480 if (iterator.hasNext()) 481 { 482 buffer.append(", "); 483 } 484 } 485 486 buffer.append("}, changesOnly="); 487 buffer.append(changesOnly); 488 buffer.append(", returnECs="); 489 buffer.append(returnECs); 490 buffer.append(", isCritical="); 491 buffer.append(isCritical()); 492 buffer.append(')'); 493 } 494}