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