001/* 002 * Copyright 2007-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2024 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) 2007-2024 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; 037 038 039 040import java.util.ArrayList; 041import java.util.Collection; 042import java.util.Iterator; 043 044import com.unboundid.asn1.ASN1StreamReader; 045import com.unboundid.asn1.ASN1StreamReaderSequence; 046import com.unboundid.ldap.protocol.LDAPResponse; 047import com.unboundid.ldap.sdk.schema.Schema; 048import com.unboundid.util.Debug; 049import com.unboundid.util.NotMutable; 050import com.unboundid.util.NotNull; 051import com.unboundid.util.Nullable; 052import com.unboundid.util.StaticUtils; 053import com.unboundid.util.ThreadSafety; 054import com.unboundid.util.ThreadSafetyLevel; 055import com.unboundid.util.Validator; 056 057import static com.unboundid.ldap.sdk.LDAPMessages.*; 058 059 060 061/** 062 * This class provides a data structure for representing an LDAP search result 063 * entry. This is a {@link ReadOnlyEntry} object that may also include zero 064 * or more controls included with the entry returned from the server. 065 */ 066@NotMutable() 067@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 068public final class SearchResultEntry 069 extends ReadOnlyEntry 070 implements LDAPResponse 071{ 072 /** 073 * The serial version UID for this serializable class. 074 */ 075 private static final long serialVersionUID = -290721544252526163L; 076 077 078 079 // The set of controls returned with this search result entry. 080 @NotNull private final Control[] controls; 081 082 // The message ID for the LDAP message containing this response. 083 private final int messageID; 084 085 086 087 /** 088 * Creates a new search result entry with the provided information. 089 * 090 * @param dn The DN for this search result entry. It must not be 091 * {@code null}. 092 * @param attributes The set of attributes to include in this search result 093 * entry. It must not be {@code null}. 094 * @param controls The set of controls for this search result entry. It 095 * must not be {@code null}. 096 */ 097 public SearchResultEntry(@NotNull final String dn, 098 @NotNull final Attribute[] attributes, 099 @NotNull final Control... controls) 100 { 101 this(-1, dn, null, attributes, controls); 102 } 103 104 105 106 /** 107 * Creates a new search result entry with the provided information. 108 * 109 * @param messageID The message ID for the LDAP message containing this 110 * response. 111 * @param dn The DN for this search result entry. It must not be 112 * {@code null}. 113 * @param attributes The set of attributes to include in this search result 114 * entry. It must not be {@code null}. 115 * @param controls The set of controls for this search result entry. It 116 * must not be {@code null}. 117 */ 118 public SearchResultEntry(final int messageID, @NotNull final String dn, 119 @NotNull final Attribute[] attributes, 120 @NotNull final Control... controls) 121 { 122 this(messageID, dn, null, attributes, controls); 123 } 124 125 126 127 /** 128 * Creates a new search result entry with the provided information. 129 * 130 * @param messageID The message ID for the LDAP message containing this 131 * response. 132 * @param dn The DN for this search result entry. It must not be 133 * {@code null}. 134 * @param schema The schema to use for operations involving this entry. 135 * It may be {@code null} if no schema is available. 136 * @param attributes The set of attributes to include in this search result 137 * entry. It must not be {@code null}. 138 * @param controls The set of controls for this search result entry. It 139 * must not be {@code null}. 140 */ 141 public SearchResultEntry(final int messageID, @NotNull final String dn, 142 @Nullable final Schema schema, 143 @NotNull final Attribute[] attributes, 144 @NotNull final Control... controls) 145 { 146 super(dn, schema, attributes); 147 148 Validator.ensureNotNull(controls); 149 150 this.messageID = messageID; 151 this.controls = controls; 152 } 153 154 155 156 /** 157 * Creates a new search result entry with the provided information. 158 * 159 * @param dn The DN for this search result entry. It must not be 160 * {@code null}. 161 * @param attributes The set of attributes to include in this search result 162 * entry. It must not be {@code null}. 163 * @param controls The set of controls for this search result entry. It 164 * must not be {@code null}. 165 */ 166 public SearchResultEntry(@NotNull final String dn, 167 @NotNull final Collection<Attribute> attributes, 168 @NotNull final Control... controls) 169 { 170 this(-1, dn, null, attributes, controls); 171 } 172 173 174 175 /** 176 * Creates a new search result entry with the provided information. 177 * 178 * @param messageID The message ID for the LDAP message containing this 179 * response. 180 * @param dn The DN for this search result entry. It must not be 181 * {@code null}. 182 * @param attributes The set of attributes to include in this search result 183 * entry. It must not be {@code null}. 184 * @param controls The set of controls for this search result entry. It 185 * must not be {@code null}. 186 */ 187 public SearchResultEntry(final int messageID, @NotNull final String dn, 188 @NotNull final Collection<Attribute> attributes, 189 @NotNull final Control... controls) 190 { 191 this(messageID, dn, null, attributes, controls); 192 } 193 194 195 196 /** 197 * Creates a new search result entry with the provided information. 198 * 199 * @param messageID The message ID for the LDAP message containing this 200 * response. 201 * @param dn The DN for this search result entry. It must not be 202 * {@code null}. 203 * @param schema The schema to use for operations involving this entry. 204 * It may be {@code null} if no schema is available. 205 * @param attributes The set of attributes to include in this search result 206 * entry. It must not be {@code null}. 207 * @param controls The set of controls for this search result entry. It 208 * must not be {@code null}. 209 */ 210 public SearchResultEntry(final int messageID, @NotNull final String dn, 211 @Nullable final Schema schema, 212 @NotNull final Collection<Attribute> attributes, 213 @NotNull final Control... controls) 214 { 215 super(dn, schema, attributes); 216 217 Validator.ensureNotNull(controls); 218 219 this.messageID = messageID; 220 this.controls = controls; 221 } 222 223 224 225 /** 226 * Creates a new search result entry from the provided entry. 227 * 228 * @param entry The entry to use to create this search result entry. It 229 * must not be {@code null}. 230 * @param controls The set of controls for this search result entry. It 231 * must not be {@code null}. 232 */ 233 public SearchResultEntry(@NotNull final Entry entry, 234 @NotNull final Control... controls) 235 { 236 this(-1, entry, controls); 237 } 238 239 240 241 /** 242 * Creates a new search result entry from the provided entry. 243 * 244 * @param messageID The message ID for the LDAP message containing this 245 * response. 246 * @param entry The entry to use to create this search result entry. It 247 * must not be {@code null}. 248 * @param controls The set of controls for this search result entry. It 249 * must not be {@code null}. 250 */ 251 public SearchResultEntry(final int messageID, @NotNull final Entry entry, 252 @NotNull final Control... controls) 253 { 254 super(entry); 255 256 Validator.ensureNotNull(controls); 257 258 this.messageID = messageID; 259 this.controls = controls; 260 } 261 262 263 264 /** 265 * Creates a new search result entry object with the protocol op and controls 266 * read from the given ASN.1 stream reader. 267 * 268 * @param messageID The message ID for the LDAP message containing 269 * this response. 270 * @param messageSequence The ASN.1 stream reader sequence used in the 271 * course of reading the LDAP message elements. 272 * @param reader The ASN.1 stream reader from which to read the 273 * protocol op and controls. 274 * @param schema The schema to use to select the appropriate 275 * matching rule to use for each attribute. It may 276 * be {@code null} if the default matching rule 277 * should always be used. 278 * 279 * @return The decoded search result entry object. 280 * 281 * @throws LDAPException If a problem occurs while reading or decoding data 282 * from the ASN.1 stream reader. 283 */ 284 @NotNull() 285 static SearchResultEntry readSearchEntryFrom(final int messageID, 286 @NotNull final ASN1StreamReaderSequence messageSequence, 287 @NotNull final ASN1StreamReader reader, 288 @Nullable final Schema schema) 289 throws LDAPException 290 { 291 try 292 { 293 reader.beginSequence(); 294 final String dn = reader.readString(); 295 296 final ArrayList<Attribute> attrList = new ArrayList<>(10); 297 final ASN1StreamReaderSequence attrSequence = reader.beginSequence(); 298 while (attrSequence.hasMoreElements()) 299 { 300 attrList.add(Attribute.readFrom(reader, schema)); 301 } 302 303 Control[] controls = NO_CONTROLS; 304 if (messageSequence.hasMoreElements()) 305 { 306 final ArrayList<Control> controlList = new ArrayList<>(5); 307 final ASN1StreamReaderSequence controlSequence = reader.beginSequence(); 308 while (controlSequence.hasMoreElements()) 309 { 310 controlList.add(Control.readFrom(reader)); 311 } 312 313 controls = new Control[controlList.size()]; 314 controlList.toArray(controls); 315 } 316 317 return new SearchResultEntry(messageID, dn, schema, attrList, controls); 318 } 319 catch (final LDAPException le) 320 { 321 Debug.debugException(le); 322 throw le; 323 } 324 catch (final Exception e) 325 { 326 Debug.debugException(e); 327 throw new LDAPException(ResultCode.DECODING_ERROR, 328 ERR_SEARCH_ENTRY_CANNOT_DECODE.get( 329 StaticUtils.getExceptionMessage(e)), 330 e); 331 } 332 } 333 334 335 336 /** 337 * {@inheritDoc} 338 */ 339 @Override() 340 public int getMessageID() 341 { 342 return messageID; 343 } 344 345 346 347 /** 348 * Retrieves the set of controls returned with this search result entry. 349 * Individual response controls of a specific type may be retrieved and 350 * decoded using the {@code get} method in the response control class. 351 * 352 * @return The set of controls returned with this search result entry. 353 */ 354 @NotNull() 355 public Control[] getControls() 356 { 357 return controls; 358 } 359 360 361 362 /** 363 * Retrieves the control with the specified OID. If there is more than one 364 * control with the given OID, then the first will be returned. 365 * 366 * @param oid The OID of the control to retrieve. 367 * 368 * @return The control with the requested OID, or {@code null} if there is no 369 * such control for this search result entry. 370 */ 371 @Nullable() 372 public Control getControl(@NotNull final String oid) 373 { 374 for (final Control c : controls) 375 { 376 if (c.getOID().equals(oid)) 377 { 378 return c; 379 } 380 } 381 382 return null; 383 } 384 385 386 387 /** 388 * Generates a hash code for this entry. 389 * 390 * @return The generated hash code for this entry. 391 */ 392 @Override() 393 public int hashCode() 394 { 395 // We need this method to satisfy checkstyle. 396 return super.hashCode(); 397 } 398 399 400 401 /** 402 * Indicates whether the provided object is equal to this entry. The provided 403 * object will only be considered equal to this entry if it is an entry with 404 * the same DN and set of attributes. 405 * 406 * @param o The object for which to make the determination. 407 * 408 * @return {@code true} if the provided object is considered equal to this 409 * entry, or {@code false} if not. 410 */ 411 @Override() 412 public boolean equals(@Nullable final Object o) 413 { 414 if (! super.equals(o)) 415 { 416 return false; 417 } 418 419 if (! (o instanceof SearchResultEntry)) 420 { 421 // When comparing a SearchResultEntry with an entry that isn't a 422 // SearchResultEntry, we'll only rely on the base entry comparison. 423 return true; 424 } 425 426 final SearchResultEntry e = (SearchResultEntry) o; 427 428 if (controls.length != e.controls.length) 429 { 430 return false; 431 } 432 433 for (int i=0; i < controls.length; i++) 434 { 435 if (! controls[i].equals(e.controls[i])) 436 { 437 return false; 438 } 439 } 440 441 return true; 442 } 443 444 445 446 /** 447 * Appends a string representation of this entry to the provided buffer. 448 * 449 * @param buffer The buffer to which to append the string representation of 450 * this entry. 451 */ 452 @Override() 453 public void toString(@NotNull final StringBuilder buffer) 454 { 455 buffer.append("SearchResultEntry(dn='"); 456 buffer.append(getDN()); 457 buffer.append('\''); 458 459 if (messageID >= 0) 460 { 461 buffer.append(", messageID="); 462 buffer.append(messageID); 463 } 464 465 buffer.append(", attributes={"); 466 467 final Iterator<Attribute> iterator = getAttributes().iterator(); 468 469 while (iterator.hasNext()) 470 { 471 iterator.next().toString(buffer); 472 if (iterator.hasNext()) 473 { 474 buffer.append(", "); 475 } 476 } 477 478 buffer.append("}, controls={"); 479 480 for (int i=0; i < controls.length; i++) 481 { 482 if (i > 0) 483 { 484 buffer.append(", "); 485 } 486 487 controls[i].toString(buffer); 488 } 489 490 buffer.append("})"); 491 } 492}