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.sdk.unboundidds.extensions;
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.ASN1Element;
047import com.unboundid.asn1.ASN1Enumerated;
048import com.unboundid.asn1.ASN1OctetString;
049import com.unboundid.asn1.ASN1Sequence;
050import com.unboundid.asn1.ASN1Integer;
051import com.unboundid.ldap.sdk.Control;
052import com.unboundid.ldap.sdk.ExtendedRequest;
053import com.unboundid.ldap.sdk.LDAPException;
054import com.unboundid.ldap.sdk.ResultCode;
055import com.unboundid.ldap.sdk.SearchScope;
056import com.unboundid.util.Debug;
057import com.unboundid.util.NotMutable;
058import com.unboundid.util.NotNull;
059import com.unboundid.util.Nullable;
060import com.unboundid.util.StaticUtils;
061import com.unboundid.util.ThreadSafety;
062import com.unboundid.util.ThreadSafetyLevel;
063import com.unboundid.util.Validator;
064
065import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
066
067
068
069/**
070 * This class provides an implementation of the stream directory values extended
071 * request as used in the Ping Identity, UnboundID, and Nokia/Alcatel-Lucent
072 * 8661 Directory Server.  It may be used to obtain all entry DNs and/or all all
073 * values for one or more attributes for a specified portion of the DIT.
074 * <BR>
075 * <BLOCKQUOTE>
076 *   <B>NOTE:</B>  This class, and other classes within the
077 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
078 *   supported for use against Ping Identity, UnboundID, and
079 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
080 *   for proprietary functionality or for external specifications that are not
081 *   considered stable or mature enough to be guaranteed to work in an
082 *   interoperable way with other types of LDAP servers.
083 * </BLOCKQUOTE>
084 * <BR>
085 * This extended request has an OID of "1.3.6.1.4.1.30221.2.6.6" and the value
086 * is encoded as follows:
087 * <PRE>
088 *   StreamDirectoryValuesRequest ::= SEQUENCE {
089 *        baseDN                [0] LDAPDN,
090 *        includeDNs            [1] DNSelection OPTIONAL,
091 *        attributes            [2] SEQUENCE OF LDAPString OPTIONAL,
092 *        valuesPerResponse     [3] INTEGER (1 .. 32767) OPTIONAL,
093 *        ... }
094 *
095 *   DNSelection ::= SEQUENCE {
096 *        scope        [0] ENUMERATED {
097 *             baseObject             (0),
098 *             singleLevel            (1),
099 *             wholeSubtree           (2),
100 *             subordinateSubtree     (3),
101 *             ... }
102 *        relative     [1] BOOLEAN DEFAULT TRUE,
103 *        ..... }
104 * </PRE>
105 */
106@NotMutable()
107@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
108public final class StreamDirectoryValuesExtendedRequest
109       extends ExtendedRequest
110{
111  /**
112   * The OID (1.3.6.1.4.1.30221.2.6.6) for the get stream directory values
113   * extended request.
114   */
115  @NotNull public static final String STREAM_DIRECTORY_VALUES_REQUEST_OID =
116       "1.3.6.1.4.1.30221.2.6.6";
117
118
119
120  /**
121   * The BER type for the baseDN element of the stream directory values request
122   * sequence.
123   */
124  private static final byte TYPE_BASE_DN = (byte) 0x80;
125
126
127
128  /**
129   * The BER type for the includeDNs element of the stream directory values
130   * request sequence.
131   */
132  private static final byte TYPE_INCLUDE_DNS = (byte) 0xA1;
133
134
135
136  /**
137   * The BER type for the attributes element of the stream directory values
138   * request sequence.
139   */
140  private static final byte TYPE_ATTRIBUTES = (byte) 0xA2;
141
142
143
144  /**
145   * The BER type for the valuesPerResponse element of the stream directory
146   * values request sequence.
147   */
148  private static final byte TYPE_VALUES_PER_RESPONSE = (byte) 0x83;
149
150
151
152  /**
153   * The BER type for the scope element of the DNSelection sequence.
154   */
155  private static final byte TYPE_SCOPE = (byte) 0x80;
156
157
158
159  /**
160   * The BER type for the relative element of the DNSelection sequence.
161   */
162  private static final byte TYPE_RELATIVE = (byte) 0x81;
163
164
165
166  /**
167   * The serial version UID for this serializable class.
168   */
169  private static final long serialVersionUID = -6365315263363449596L;
170
171
172
173  // Indicates whether to return DN values that are relative to the base DN.
174  private final boolean returnRelativeDNs;
175
176  // The maximum number of values to include per response.
177  private final int valuesPerResponse;
178
179  // The list of attribute values to be returned.
180  @NotNull private final List<String> attributes;
181
182  // The search scope to use if DN values are to be included.
183  @Nullable private final SearchScope dnScope;
184
185  // The base DN for this stream directory values request.
186  @NotNull private final String baseDN;
187
188
189
190  /**
191   * Creates a new stream directory values extended request with the provided
192   * information.
193   *
194   * @param  baseDN             The base DN which indicates the portion of the
195   *                            DIT to target.  It must not be {@code null}.
196   * @param  dnScope            The scope for which to return information about
197   *                            entry DNs in the specified portion of the DIT.
198   *                            This may be {@code null} if information about
199   *                            entry DNs should not be returned.
200   * @param  returnRelativeDNs  Indicates whether DNs returned should be
201   *                            relative to the base DN rather than full DNs.
202   * @param  attributes         The names of the attributes for which to
203   *                            retrieve the values.  This may be {@code null}
204   *                            or empty if only entry DNs should be retrieved.
205   * @param  valuesPerResponse  The maximum number of values to include per
206   *                            response.  A value less than or equal to zero
207   *                            indicates that the server should choose an
208   *                            appropriate value.
209   * @param  controls           The set of controls to include in the request.
210   *                            It may be {@code null} or empty if no controls
211   *                            should be included in the request.
212   */
213  public StreamDirectoryValuesExtendedRequest(@NotNull final String baseDN,
214              @Nullable final SearchScope dnScope,
215              final boolean returnRelativeDNs,
216              @Nullable final List<String> attributes,
217              final int valuesPerResponse,
218              @Nullable final Control... controls)
219  {
220    super(STREAM_DIRECTORY_VALUES_REQUEST_OID,
221         encodeValue(baseDN, dnScope, returnRelativeDNs, attributes,
222                     valuesPerResponse),
223         controls);
224
225    this.baseDN            = baseDN;
226    this.dnScope           = dnScope;
227    this.returnRelativeDNs = returnRelativeDNs;
228
229    if (attributes == null)
230    {
231      this.attributes = Collections.emptyList();
232    }
233    else
234    {
235      this.attributes = Collections.unmodifiableList(attributes);
236    }
237
238    if (valuesPerResponse < 0)
239    {
240      this.valuesPerResponse = 0;
241    }
242    else
243    {
244      this.valuesPerResponse = valuesPerResponse;
245    }
246  }
247
248
249
250  /**
251   * Creates a new stream directory values extended request from the provided
252   * generic extended request.
253   *
254   * @param  extendedRequest  The generic extended request to use to create this
255   *                          stream directory values extended request.
256   *
257   * @throws  LDAPException  If a problem occurs while decoding the request.
258   */
259  public StreamDirectoryValuesExtendedRequest(
260              @NotNull final ExtendedRequest extendedRequest)
261         throws LDAPException
262  {
263    super(extendedRequest);
264
265    final ASN1OctetString value = extendedRequest.getValue();
266    if (value == null)
267    {
268      throw new LDAPException(ResultCode.DECODING_ERROR,
269           ERR_STREAM_DIRECTORY_VALUES_REQUEST_NO_VALUE.get());
270    }
271
272    boolean                 tmpRelative  = true;
273    int                     tmpNumValues = 0;
274    final ArrayList<String> tmpAttrs     = new ArrayList<>(10);
275    SearchScope             tmpScope     = null;
276    String                  tmpBaseDN    = null;
277
278    try
279    {
280      final ASN1Element[] svElements =
281           ASN1Element.decode(value.getValue()).decodeAsSequence().elements();
282      for (final ASN1Element svElement : svElements)
283      {
284        switch (svElement.getType())
285        {
286          case TYPE_BASE_DN:
287            tmpBaseDN = svElement.decodeAsOctetString().stringValue();
288            break;
289
290          case TYPE_INCLUDE_DNS:
291            final ASN1Element[] idElements =
292                 svElement.decodeAsSequence().elements();
293            for (final ASN1Element idElement : idElements)
294            {
295              switch (idElement.getType())
296              {
297                case TYPE_SCOPE:
298                  final int scopeValue =
299                       idElement.decodeAsEnumerated().intValue();
300                  tmpScope = SearchScope.definedValueOf(scopeValue);
301                  if (tmpScope == null)
302                  {
303                    throw new LDAPException(ResultCode.DECODING_ERROR,
304                         ERR_STREAM_DIRECTORY_VALUES_REQUEST_INVALID_SCOPE.get(
305                              scopeValue));
306                  }
307                  break;
308                case TYPE_RELATIVE:
309                  tmpRelative =
310                       idElement.decodeAsBoolean().booleanValue();
311                  break;
312                default:
313                  throw new LDAPException(ResultCode.DECODING_ERROR,
314                  ERR_STREAM_DIRECTORY_VALUES_REQUEST_INVALID_INCLUDE_DNS_TYPE.
315                       get(StaticUtils.toHex(idElement.getType())));
316              }
317            }
318            break;
319
320          case TYPE_ATTRIBUTES:
321            final ASN1Element[] attrElements =
322                 svElement.decodeAsSequence().elements();
323            for (final ASN1Element attrElement : attrElements)
324            {
325              tmpAttrs.add(attrElement.decodeAsOctetString().stringValue());
326            }
327            break;
328
329          case TYPE_VALUES_PER_RESPONSE:
330            tmpNumValues = svElement.decodeAsInteger().intValue();
331            if (tmpNumValues < 0)
332            {
333              tmpNumValues = 0;
334            }
335            break;
336
337          default:
338            throw new LDAPException(ResultCode.DECODING_ERROR,
339                 ERR_STREAM_DIRECTORY_VALUES_REQUEST_INVALID_SEQUENCE_TYPE.get(
340                      StaticUtils.toHex(svElement.getType())));
341        }
342      }
343    }
344    catch (final LDAPException le)
345    {
346      throw le;
347    }
348    catch (final Exception e)
349    {
350      Debug.debugException(e);
351      throw new LDAPException(ResultCode.DECODING_ERROR,
352           ERR_STREAM_DIRECTORY_VALUES_REQUEST_CANNOT_DECODE.get(
353                StaticUtils.getExceptionMessage(e)), e);
354    }
355
356    if (tmpBaseDN == null)
357    {
358      throw new LDAPException(ResultCode.DECODING_ERROR,
359           ERR_STREAM_DIRECTORY_VALUES_REQUEST_NO_BASE_DN.get());
360    }
361
362    baseDN            = tmpBaseDN;
363    dnScope           = tmpScope;
364    returnRelativeDNs = tmpRelative;
365    attributes        = Collections.unmodifiableList(tmpAttrs);
366    valuesPerResponse = tmpNumValues;
367  }
368
369
370
371  /**
372   * Encodes the provided information into a form suitable for use as the value
373   * of this extended request.
374   *
375   * @param  baseDN             The base DN which indicates the portion of the
376   *                            DIT to target.
377   * @param  scope              The scope for which to return information about
378   *                            entry DNs in the specified portion of the DIT.
379   *                            This may be {@code null} if information about
380   *                            entry DNs should not be returned.
381   * @param  relativeDNs        Indicates whether DNs returned should be
382   *                            relative to the base DN rather than full DNs.
383   * @param  attributes         The names of the attributes for which to
384   *                            retrieve the values.  This may be {@code null}
385   *                            or empty if only entry DNs should be retrieved.
386   * @param  valuesPerResponse  The maximum number of values to include per
387   *                            response.  A value less than or equal to zero
388   *                            indicates that the server should choose an
389   *                            appropriate value.
390   *
391   * @return  The ASN.1 octet string containing the encoded value to use for
392   *          this extended request.
393   */
394  @NotNull()
395  private static ASN1OctetString encodeValue(@NotNull final String baseDN,
396               @Nullable final SearchScope scope,
397               final boolean relativeDNs,
398               @Nullable final List<String> attributes,
399               final int valuesPerResponse)
400  {
401    Validator.ensureNotNull(baseDN);
402
403    final ArrayList<ASN1Element> svElements = new ArrayList<>(4);
404    svElements.add(new ASN1OctetString(TYPE_BASE_DN, baseDN));
405
406    if (scope != null)
407    {
408      final ArrayList<ASN1Element> idElements = new ArrayList<>(2);
409      idElements.add(new ASN1Enumerated(TYPE_SCOPE, scope.intValue()));
410
411      if (! relativeDNs)
412      {
413        idElements.add(new ASN1Boolean(TYPE_RELATIVE, relativeDNs));
414      }
415
416      svElements.add(new ASN1Sequence(TYPE_INCLUDE_DNS, idElements));
417    }
418
419    if ((attributes != null) && (! attributes.isEmpty()))
420    {
421      final ArrayList<ASN1Element> attrElements =
422           new ArrayList<>(attributes.size());
423      for (final String s : attributes)
424      {
425        attrElements.add(new ASN1OctetString(s));
426      }
427      svElements.add(new ASN1Sequence(TYPE_ATTRIBUTES, attrElements));
428    }
429
430    if (valuesPerResponse > 0)
431    {
432      svElements.add(new ASN1Integer(TYPE_VALUES_PER_RESPONSE,
433                                     valuesPerResponse));
434    }
435
436    return new ASN1OctetString(new ASN1Sequence(svElements).encode());
437  }
438
439
440
441  /**
442   * Retrieves the base DN for this request.
443   *
444   * @return  The base DN for this request.
445   */
446  @NotNull()
447  public String getBaseDN()
448  {
449    return baseDN;
450  }
451
452
453
454  /**
455   * Retrieves the scope for entry DNs to be included in intermediate responses.
456   *
457   * @return  The scope for entry DNs to be included in intermediate responses,
458   *          or {@code null} if information about entry DNs should not be
459   *          returned.
460   */
461  @Nullable()
462  public SearchScope getDNScope()
463  {
464    return dnScope;
465  }
466
467
468
469  /**
470   * Indicates whether entry DN values returned should be relative to the
471   * provided base DN.
472   *
473   * @return  {@code true} if entry DN values returned should be relative to the
474   *          provided base DN, or {@code false} if they should be complete DNs.
475   */
476  public boolean returnRelativeDNs()
477  {
478    return returnRelativeDNs;
479  }
480
481
482
483  /**
484   * Retrieves the list of names of attributes whose values should be returned
485   * to the client.
486   *
487   * @return  The list of names of attributes whose values should be returned to
488   *          the client, or an empty list if only information about entry DNs
489   *          should be returned.
490   */
491  @NotNull()
492  public List<String> getAttributes()
493  {
494    return attributes;
495  }
496
497
498
499  /**
500   * Retrieves the maximum number of values that should be included in each
501   * stream directory values intermediate response.
502   *
503   * @return  The maximum number of values that should be included in each
504   *          stream directory values intermediate response, or 0 if the server
505   *          should choose the appropriate number of values per response.
506   */
507  public int getValuesPerResponse()
508  {
509    return valuesPerResponse;
510  }
511
512
513
514  /**
515   * {@inheritDoc}
516   */
517  @Override()
518  @NotNull()
519  public StreamDirectoryValuesExtendedRequest duplicate()
520  {
521    return duplicate(getControls());
522  }
523
524
525
526  /**
527   * {@inheritDoc}
528   */
529  @Override()
530  @NotNull()
531  public StreamDirectoryValuesExtendedRequest duplicate(
532              @Nullable final Control[] controls)
533  {
534    final StreamDirectoryValuesExtendedRequest r =
535         new StreamDirectoryValuesExtendedRequest(baseDN, dnScope,
536              returnRelativeDNs, attributes, valuesPerResponse, controls);
537    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
538    r.setIntermediateResponseListener(getIntermediateResponseListener());
539    r.setReferralDepth(getReferralDepth());
540    r.setReferralConnector(getReferralConnectorInternal());
541    return r;
542  }
543
544
545
546  /**
547   * {@inheritDoc}
548   */
549  @Override()
550  @NotNull()
551  public String getExtendedRequestName()
552  {
553    return INFO_EXTENDED_REQUEST_NAME_STREAM_DIRECTORY_VALUES.get();
554  }
555
556
557
558  /**
559   * {@inheritDoc}
560   */
561  @Override()
562  public void toString(@NotNull final StringBuilder buffer)
563  {
564    buffer.append("StreamDirectoryValuesExtendedRequest(baseDN='");
565    buffer.append(baseDN);
566    buffer.append('\'');
567
568    if (dnScope != null)
569    {
570      buffer.append(", scope='");
571      buffer.append(dnScope.getName());
572      buffer.append("', returnRelativeDNs=");
573      buffer.append(returnRelativeDNs);
574    }
575
576    buffer.append(", attributes={");
577    if (! attributes.isEmpty())
578    {
579      final Iterator<String> iterator = attributes.iterator();
580      while (iterator.hasNext())
581      {
582        buffer.append('\'');
583        buffer.append(iterator.next());
584        buffer.append('\'');
585
586        if (iterator.hasNext())
587        {
588          buffer.append(", ");
589        }
590      }
591    }
592    buffer.append('}');
593
594    if (valuesPerResponse > 0)
595    {
596      buffer.append(", valuesPerResponse=");
597      buffer.append(valuesPerResponse);
598    }
599
600    final Control[] controls = getControls();
601    if (controls.length > 0)
602    {
603      buffer.append(", controls={");
604      for (int i=0; i < controls.length; i++)
605      {
606        if (i > 0)
607        {
608          buffer.append(", ");
609        }
610
611        buffer.append(controls[i]);
612      }
613      buffer.append('}');
614    }
615
616    buffer.append(')');
617  }
618}