001/*
002 * Copyright 2009-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2009-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) 2009-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.protocol;
037
038
039
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.Iterator;
043import java.util.List;
044
045import com.unboundid.asn1.ASN1Boolean;
046import com.unboundid.asn1.ASN1Buffer;
047import com.unboundid.asn1.ASN1BufferSequence;
048import com.unboundid.asn1.ASN1Element;
049import com.unboundid.asn1.ASN1Enumerated;
050import com.unboundid.asn1.ASN1Integer;
051import com.unboundid.asn1.ASN1OctetString;
052import com.unboundid.asn1.ASN1Sequence;
053import com.unboundid.asn1.ASN1StreamReader;
054import com.unboundid.asn1.ASN1StreamReaderSequence;
055import com.unboundid.ldap.sdk.Control;
056import com.unboundid.ldap.sdk.DereferencePolicy;
057import com.unboundid.ldap.sdk.Filter;
058import com.unboundid.ldap.sdk.LDAPException;
059import com.unboundid.ldap.sdk.ResultCode;
060import com.unboundid.ldap.sdk.SearchRequest;
061import com.unboundid.ldap.sdk.SearchScope;
062import com.unboundid.util.Debug;
063import com.unboundid.util.InternalUseOnly;
064import com.unboundid.util.NotMutable;
065import com.unboundid.util.NotNull;
066import com.unboundid.util.Nullable;
067import com.unboundid.util.StaticUtils;
068import com.unboundid.util.ThreadSafety;
069import com.unboundid.util.ThreadSafetyLevel;
070
071import static com.unboundid.ldap.protocol.ProtocolMessages.*;
072
073
074
075/**
076 * This class provides an implementation of an LDAP search request protocol op.
077 */
078@InternalUseOnly()
079@NotMutable()
080@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
081public final class SearchRequestProtocolOp
082       implements ProtocolOp
083{
084  /**
085   * The serial version UID for this serializable class.
086   */
087  private static final long serialVersionUID = -8521750809606744181L;
088
089
090
091  // The typesOnly flag for this search request.
092  private final boolean typesOnly;
093
094  // The dereference policy for this search request.
095  @NotNull private final DereferencePolicy derefPolicy;
096
097  // The filter for this search request.
098  @NotNull private final Filter filter;
099
100  // The size limit for this search request.
101  private final int sizeLimit;
102
103  // The time limit for this search request.
104  private final int timeLimit;
105
106  // The set of attributes for this search request.
107  @NotNull private final List<String> attributes;
108
109  // The scope for this search request.
110  @NotNull private final SearchScope scope;
111
112  // The base DN for this search request.
113  @NotNull private final String baseDN;
114
115
116
117  /**
118   * Creates a new search request protocol op with the provided information.
119   *
120   * @param  baseDN       The base DN for this search request.
121   * @param  scope        The scope for this search request.
122   * @param  derefPolicy  The policy to use for aliases encountered during the
123   *                      search.
124   * @param  sizeLimit    The maximum number of entries to return for the
125   *                      search, or zero for no limit.
126   * @param  timeLimit    The maximum length of time to spend processing the
127   *                      search, or zero for no limit.
128   * @param  typesOnly    Indicates whether to return only attribute types or
129   *                      both types and values.
130   * @param  filter       The filter for this search request.
131   * @param  attributes   The names of attributes to include in matching
132   *                      entries.
133   */
134  public SearchRequestProtocolOp(@NotNull final String baseDN,
135              @NotNull final SearchScope scope,
136              @NotNull final DereferencePolicy derefPolicy, final int sizeLimit,
137              final int timeLimit, final boolean typesOnly,
138              @NotNull final Filter filter,
139              @Nullable final List<String> attributes)
140  {
141    this.scope       = scope;
142    this.derefPolicy = derefPolicy;
143    this.typesOnly   = typesOnly;
144    this.filter      = filter;
145
146    if (baseDN == null)
147    {
148      this.baseDN = "";
149    }
150    else
151    {
152      this.baseDN = baseDN;
153    }
154
155    if (sizeLimit > 0)
156    {
157      this.sizeLimit = sizeLimit;
158    }
159    else
160    {
161      this.sizeLimit = 0;
162    }
163
164    if (timeLimit > 0)
165    {
166      this.timeLimit = timeLimit;
167    }
168    else
169    {
170      this.timeLimit = 0;
171    }
172
173    if (attributes == null)
174    {
175      this.attributes = Collections.emptyList();
176    }
177    else
178    {
179      this.attributes = Collections.unmodifiableList(attributes);
180    }
181  }
182
183
184
185  /**
186   * Creates a new search request protocol op from the provided search request
187   * object.
188   *
189   * @param  request  The search request object to use to create this protocol
190   *                  op.
191   */
192  public SearchRequestProtocolOp(@NotNull final SearchRequest request)
193  {
194    baseDN      = request.getBaseDN();
195    scope       = request.getScope();
196    derefPolicy = request.getDereferencePolicy();
197    sizeLimit   = request.getSizeLimit();
198    timeLimit   = request.getTimeLimitSeconds();
199    typesOnly   = request.typesOnly();
200    filter      = request.getFilter();
201    attributes  = request.getAttributeList();
202  }
203
204
205
206  /**
207   * Creates a new search request protocol op read from the provided ASN.1
208   * stream reader.
209   *
210   * @param  reader  The ASN.1 stream reader from which to read the search
211   *                 request protocol op.
212   *
213   * @throws  LDAPException  If a problem occurs while reading or parsing the
214   *                         search request.
215   */
216  SearchRequestProtocolOp(@NotNull final ASN1StreamReader reader)
217       throws LDAPException
218  {
219    try
220    {
221      reader.beginSequence();
222      baseDN      = reader.readString();
223      scope       = SearchScope.valueOf(reader.readEnumerated());
224      derefPolicy = DereferencePolicy.valueOf(reader.readEnumerated());
225      sizeLimit   = reader.readInteger();
226      timeLimit   = reader.readInteger();
227      typesOnly   = reader.readBoolean();
228      filter      = Filter.readFrom(reader);
229
230      final ArrayList<String> attrs = new ArrayList<>(5);
231      final ASN1StreamReaderSequence attrSequence = reader.beginSequence();
232      while (attrSequence.hasMoreElements())
233      {
234        attrs.add(reader.readString());
235      }
236
237      attributes = Collections.unmodifiableList(attrs);
238    }
239    catch (final LDAPException le)
240    {
241      Debug.debugException(le);
242      throw le;
243    }
244    catch (final Exception e)
245    {
246      Debug.debugException(e);
247
248      throw new LDAPException(ResultCode.DECODING_ERROR,
249           ERR_SEARCH_REQUEST_CANNOT_DECODE.get(
250                StaticUtils.getExceptionMessage(e)),
251           e);
252    }
253  }
254
255
256
257  /**
258   * Retrieves the base DN for this search request.
259   *
260   * @return  The base DN for this search request.
261   */
262  @NotNull()
263  public String getBaseDN()
264  {
265    return baseDN;
266  }
267
268
269
270  /**
271   * Retrieves the scope for this search request.
272   *
273   * @return  The scope for this search request.
274   */
275  @NotNull()
276  public SearchScope getScope()
277  {
278    return scope;
279  }
280
281
282
283  /**
284   * Retrieves the policy to use for any aliases encountered during the search.
285   *
286   * @return  The policy to use for any aliases encountered during the search.
287   */
288  @NotNull()
289  public DereferencePolicy getDerefPolicy()
290  {
291    return derefPolicy;
292  }
293
294
295
296  /**
297   * Retrieves the maximum number of entries that the server should return for
298   * the search.
299   *
300   * @return  The maximum number of entries that the server should return for
301   *          the search, or zero if there is no limit.
302   */
303  public int getSizeLimit()
304  {
305    return sizeLimit;
306  }
307
308
309
310  /**
311   * Retrieves the maximum length of time in seconds the server should spend
312   * processing the search.
313   *
314   * @return  The maximum length of time in seconds the server should spend
315   *          processing the search, or zero if there is no limit.
316   */
317  public int getTimeLimit()
318  {
319    return timeLimit;
320  }
321
322
323
324  /**
325   * Indicates whether the server should return only attribute types or both
326   * attribute types and values.
327   *
328   * @return  {@code true} if the server should return only attribute types, or
329   *          {@code false} if both types and values should be returned.
330   */
331  public boolean typesOnly()
332  {
333    return typesOnly;
334  }
335
336
337
338  /**
339   * Retrieves the filter for this search request.
340   *
341   * @return  The filter for this search request.
342   */
343  @NotNull()
344  public Filter getFilter()
345  {
346    return filter;
347  }
348
349
350
351  /**
352   * Retrieves the set of requested attributes for this search request.
353   *
354   * @return  The set of requested attributes for this search request.
355   */
356  @NotNull()
357  public List<String> getAttributes()
358  {
359    return attributes;
360  }
361
362
363
364  /**
365   * {@inheritDoc}
366   */
367  @Override()
368  public byte getProtocolOpType()
369  {
370    return LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST;
371  }
372
373
374
375  /**
376   * {@inheritDoc}
377   */
378  @Override()
379  @NotNull()
380  public ASN1Element encodeProtocolOp()
381  {
382    final ArrayList<ASN1Element> attrElements =
383         new ArrayList<>(attributes.size());
384    for (final String attribute : attributes)
385    {
386      attrElements.add(new ASN1OctetString(attribute));
387    }
388
389    return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST,
390         new ASN1OctetString(baseDN),
391         new ASN1Enumerated(scope.intValue()),
392         new ASN1Enumerated(derefPolicy.intValue()),
393         new ASN1Integer(sizeLimit),
394         new ASN1Integer(timeLimit),
395         new ASN1Boolean(typesOnly),
396         filter.encode(),
397         new ASN1Sequence(attrElements));
398  }
399
400
401
402  /**
403   * Decodes the provided ASN.1 element as a search request protocol op.
404   *
405   * @param  element  The ASN.1 element to be decoded.
406   *
407   * @return  The decoded search request protocol op.
408   *
409   * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
410   *                         a search request protocol op.
411   */
412  @NotNull()
413  public static SearchRequestProtocolOp decodeProtocolOp(
414                                             @NotNull final ASN1Element element)
415         throws LDAPException
416  {
417    try
418    {
419      final ASN1Element[] elements =
420           ASN1Sequence.decodeAsSequence(element).elements();
421      final String baseDN =
422           ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
423      final SearchScope scope = SearchScope.valueOf(
424           ASN1Enumerated.decodeAsEnumerated(elements[1]).intValue());
425      final DereferencePolicy derefPolicy = DereferencePolicy.valueOf(
426           ASN1Enumerated.decodeAsEnumerated(elements[2]).intValue());
427      final int sizeLimit = ASN1Integer.decodeAsInteger(elements[3]).intValue();
428      final int timeLimit = ASN1Integer.decodeAsInteger(elements[4]).intValue();
429      final boolean typesOnly =
430           ASN1Boolean.decodeAsBoolean(elements[5]).booleanValue();
431      final Filter filter = Filter.decode(elements[6]);
432
433      final ASN1Element[] attrElements =
434           ASN1Sequence.decodeAsSequence(elements[7]).elements();
435      final ArrayList<String> attributes = new ArrayList<>(attrElements.length);
436      for (final ASN1Element e : attrElements)
437      {
438        attributes.add(ASN1OctetString.decodeAsOctetString(e).stringValue());
439      }
440
441      return new SearchRequestProtocolOp(baseDN, scope, derefPolicy, sizeLimit,
442           timeLimit, typesOnly, filter, attributes);
443    }
444    catch (final Exception e)
445    {
446      Debug.debugException(e);
447      throw new LDAPException(ResultCode.DECODING_ERROR,
448           ERR_SEARCH_REQUEST_CANNOT_DECODE.get(
449                StaticUtils.getExceptionMessage(e)),
450           e);
451    }
452  }
453
454
455
456  /**
457   * {@inheritDoc}
458   */
459  @Override()
460  public void writeTo(@NotNull final ASN1Buffer buffer)
461  {
462    final ASN1BufferSequence opSequence =
463         buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_SEARCH_REQUEST);
464    buffer.addOctetString(baseDN);
465    buffer.addEnumerated(scope.intValue());
466    buffer.addEnumerated(derefPolicy.intValue());
467    buffer.addInteger(sizeLimit);
468    buffer.addInteger(timeLimit);
469    buffer.addBoolean(typesOnly);
470    filter.writeTo(buffer);
471
472    final ASN1BufferSequence attrSequence = buffer.beginSequence();
473    for (final String s : attributes)
474    {
475      buffer.addOctetString(s);
476    }
477    attrSequence.end();
478    opSequence.end();
479  }
480
481
482
483  /**
484   * Creates a search request from this protocol op.
485   *
486   * @param  controls  The set of controls to include in the search request.
487   *                   It may be empty or {@code null} if no controls should be
488   *                   included.
489   *
490   * @return  The search request that was created.
491   */
492  @NotNull()
493  public SearchRequest toSearchRequest(@Nullable final Control... controls)
494  {
495    final String[] attrArray = new String[attributes.size()];
496    attributes.toArray(attrArray);
497
498    return new SearchRequest(null, controls, baseDN, scope, derefPolicy,
499         sizeLimit, timeLimit, typesOnly, filter, attrArray);
500  }
501
502
503
504  /**
505   * Retrieves a string representation of this protocol op.
506   *
507   * @return  A string representation of this protocol op.
508   */
509  @Override()
510  @NotNull()
511  public String toString()
512  {
513    final StringBuilder buffer = new StringBuilder();
514    toString(buffer);
515    return buffer.toString();
516  }
517
518
519
520  /**
521   * {@inheritDoc}
522   */
523  @Override()
524  public void toString(@NotNull final StringBuilder buffer)
525  {
526    buffer.append("SearchRequestProtocolOp(baseDN='");
527    buffer.append(baseDN);
528    buffer.append("', scope='");
529    buffer.append(scope.toString());
530    buffer.append("', derefPolicy='");
531    buffer.append(derefPolicy.toString());
532    buffer.append("', sizeLimit=");
533    buffer.append(sizeLimit);
534    buffer.append(", timeLimit=");
535    buffer.append(timeLimit);
536    buffer.append(", typesOnly=");
537    buffer.append(typesOnly);
538    buffer.append(", filter='");
539    filter.toString(buffer);
540    buffer.append("', attributes={");
541
542    final Iterator<String> iterator = attributes.iterator();
543    while (iterator.hasNext())
544    {
545      buffer.append(iterator.next());
546      if (iterator.hasNext())
547      {
548        buffer.append(',');
549      }
550    }
551
552    buffer.append("})");
553  }
554}