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