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