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}