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