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 proxy values extended
071 * request as used in the Ping Identity, UnboundID, Nokia/Alcatel-Lucent 8661
072 * Directory Proxy Server.  It may be used to obtain all entry DNs and/or 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.8" and the value
086 * is encoded as follows:
087 * <PRE>
088 *   StreamProxyValuesRequest ::= 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 *        backendSets           [4] SEQUENCE OF BackendSetConfig,
094 *        ... }
095 *
096 *   DNSelection ::= SEQUENCE {
097 *        scope        [0] ENUMERATED {
098 *             baseObject             (0),
099 *             singleLevel            (1),
100 *             wholeSubtree           (2),
101 *             subordinateSubtree     (3),
102 *             ... }
103 *        relative     [1] BOOLEAN DEFAULT TRUE,
104 *        ..... }
105 *
106 *   BackendSetConfig ::= SEQUENCE {
107 *        backendSetID       OCTET STRING,
108 *        backendServers     SEQUENCE OF SEQUENCE {
109 *             host     OCTET STRING,
110 *             port     INTEGER (1 .. 65535) } }
111 * </PRE>
112 */
113@NotMutable()
114@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
115public final class StreamProxyValuesExtendedRequest
116       extends ExtendedRequest
117{
118  /**
119   * The OID (1.3.6.1.4.1.30221.2.6.8) for the get stream proxy values extended
120   * request.
121   */
122  @NotNull public static final String STREAM_PROXY_VALUES_REQUEST_OID =
123       "1.3.6.1.4.1.30221.2.6.8";
124
125
126
127  /**
128   * The BER type for the baseDN element of the stream proxy values request
129   * sequence.
130   */
131  private static final byte TYPE_BASE_DN = (byte) 0x80;
132
133
134
135  /**
136   * The BER type for the includeDNs element of the stream proxy values request
137   * sequence.
138   */
139  private static final byte TYPE_INCLUDE_DNS = (byte) 0xA1;
140
141
142
143  /**
144   * The BER type for the attributes element of the stream proxy values request
145   * sequence.
146   */
147  private static final byte TYPE_ATTRIBUTES = (byte) 0xA2;
148
149
150
151  /**
152   * The BER type for the valuesPerResponse element of the stream proxy values
153   * request sequence.
154   */
155  private static final byte TYPE_VALUES_PER_RESPONSE = (byte) 0x83;
156
157
158
159  /**
160   * The BER type for the backendSets element of the stream proxy values request
161   * sequence.
162   */
163  private static final byte TYPE_BACKEND_SETS = (byte) 0xA4;
164
165
166
167  /**
168   * The BER type for the scope element of the DNSelection sequence.
169   */
170  private static final byte TYPE_SCOPE = (byte) 0x80;
171
172
173
174  /**
175   * The BER type for the relative element of the DNSelection sequence.
176   */
177  private static final byte TYPE_RELATIVE = (byte) 0x81;
178
179
180
181  /**
182   * The serial version UID for this serializable class.
183   */
184  private static final long serialVersionUID = 2528621021697410806L;
185
186
187
188  // Indicates whether to return DN values that are relative to the base DN.
189  private final boolean returnRelativeDNs;
190
191  // The maximum number of values to include per response.
192  private final int valuesPerResponse;
193
194  // The list of backend sets defined in the Directory Proxy Server issuing the
195  // request.
196  @NotNull private final List<StreamProxyValuesBackendSet> backendSets;
197
198  // The list of attribute values to be returned.
199  @NotNull private final List<String> attributes;
200
201  // The search scope to use if DN values are to be included.
202  @Nullable private final SearchScope dnScope;
203
204  // The base DN for this stream proxy values request.
205  @NotNull private final String baseDN;
206
207
208
209  /**
210   * Creates a new stream proxy values extended request with the provided
211   * information.
212   *
213   * @param  baseDN             The base DN which indicates the portion of the
214   *                            DIT to target.  It must not be {@code null}.
215   * @param  dnScope            The scope for which to return information about
216   *                            entry DNs in the specified portion of the DIT.
217   *                            This may be {@code null} if information about
218   *                            entry DNs should not be returned.
219   * @param  returnRelativeDNs  Indicates whether DNs returned should be
220   *                            relative to the base DN rather than full DNs.
221   * @param  attributes         The names of the attributes for which to
222   *                            retrieve the values.  This may be {@code null}
223   *                            or empty if only entry DNs should be retrieved.
224   * @param  valuesPerResponse  The maximum number of values to include per
225   *                            response.  A value less than or equal to zero
226   *                            indicates that the server should choose an
227   *                            appropriate value.
228   * @param  backendSets        The list of backend sets defined in the
229   *                            Directory Proxy Server issuing the request.  It
230   *                            must not be {@code null} or empty.
231   * @param  controls           The set of controls to include in the request.
232   *                            It may be {@code null} or empty if no controls
233   *                            should be included in the request.
234   */
235  public StreamProxyValuesExtendedRequest(@NotNull final String baseDN,
236              @Nullable final SearchScope dnScope,
237              final boolean returnRelativeDNs,
238              @Nullable final List<String> attributes,
239              final int valuesPerResponse,
240              @NotNull final List<StreamProxyValuesBackendSet> backendSets,
241              @Nullable final Control... controls)
242  {
243    super(STREAM_PROXY_VALUES_REQUEST_OID,
244         encodeValue(baseDN, dnScope, returnRelativeDNs, attributes,
245                     valuesPerResponse, backendSets),
246         controls);
247
248    this.baseDN            = baseDN;
249    this.dnScope           = dnScope;
250    this.returnRelativeDNs = returnRelativeDNs;
251    this.backendSets       = Collections.unmodifiableList(backendSets);
252
253    if (attributes == null)
254    {
255      this.attributes = Collections.emptyList();
256    }
257    else
258    {
259      this.attributes = Collections.unmodifiableList(attributes);
260    }
261
262    if (valuesPerResponse < 0)
263    {
264      this.valuesPerResponse = 0;
265    }
266    else
267    {
268      this.valuesPerResponse = valuesPerResponse;
269    }
270  }
271
272
273
274  /**
275   * Creates a new stream proxy values extended request from the provided
276   * generic extended request.
277   *
278   * @param  extendedRequest  The generic extended request to use to create this
279   *                          stream proxy values extended request.
280   *
281   * @throws  LDAPException  If a problem occurs while decoding the request.
282   */
283  public StreamProxyValuesExtendedRequest(
284              @NotNull final ExtendedRequest extendedRequest)
285         throws LDAPException
286  {
287    super(extendedRequest);
288
289    final ASN1OctetString value = extendedRequest.getValue();
290    if (value == null)
291    {
292      throw new LDAPException(ResultCode.DECODING_ERROR,
293           ERR_STREAM_PROXY_VALUES_REQUEST_NO_VALUE.get());
294    }
295
296    boolean                 tmpRelative  = true;
297    int                     tmpNumValues = 0;
298    final ArrayList<String> tmpAttrs     = new ArrayList<>(10);
299    SearchScope             tmpScope     = null;
300    String                  tmpBaseDN    = null;
301
302    final ArrayList<StreamProxyValuesBackendSet> tmpBackendSets =
303         new ArrayList<>(10);
304
305    try
306    {
307      final ASN1Element[] svElements =
308           ASN1Element.decode(value.getValue()).decodeAsSequence().elements();
309      for (final ASN1Element svElement : svElements)
310      {
311        switch (svElement.getType())
312        {
313          case TYPE_BASE_DN:
314            tmpBaseDN = svElement.decodeAsOctetString().stringValue();
315            break;
316
317          case TYPE_INCLUDE_DNS:
318            final ASN1Element[] idElements =
319                 svElement.decodeAsSequence().elements();
320            for (final ASN1Element idElement : idElements)
321            {
322              switch (idElement.getType())
323              {
324                case TYPE_SCOPE:
325                  final int scopeValue =
326                       idElement.decodeAsEnumerated().intValue();
327                  tmpScope = SearchScope.definedValueOf(scopeValue);
328                  if (tmpScope == null)
329                  {
330                    throw new LDAPException(ResultCode.DECODING_ERROR,
331                         ERR_STREAM_PROXY_VALUES_REQUEST_INVALID_SCOPE.get(
332                              scopeValue));
333                  }
334                  break;
335                case TYPE_RELATIVE:
336                  tmpRelative =
337                       idElement.decodeAsBoolean().booleanValue();
338                  break;
339                default:
340                  throw new LDAPException(ResultCode.DECODING_ERROR,
341                  ERR_STREAM_PROXY_VALUES_REQUEST_INVALID_INCLUDE_DNS_TYPE.
342                       get(StaticUtils.toHex(idElement.getType())));
343              }
344            }
345            break;
346
347          case TYPE_ATTRIBUTES:
348            final ASN1Element[] attrElements =
349                 svElement.decodeAsSequence().elements();
350            for (final ASN1Element attrElement : attrElements)
351            {
352              tmpAttrs.add(attrElement.decodeAsOctetString().stringValue());
353            }
354            break;
355
356          case TYPE_VALUES_PER_RESPONSE:
357            tmpNumValues = svElement.decodeAsInteger().intValue();
358            if (tmpNumValues < 0)
359            {
360              tmpNumValues = 0;
361            }
362            break;
363
364          case TYPE_BACKEND_SETS:
365            final ASN1Element[] backendSetElements =
366                 svElement.decodeAsSequence().elements();
367            for (final ASN1Element setElement : backendSetElements)
368            {
369              tmpBackendSets.add(
370                   StreamProxyValuesBackendSet.decode(setElement));
371            }
372            break;
373
374          default:
375            throw new LDAPException(ResultCode.DECODING_ERROR,
376                 ERR_STREAM_PROXY_VALUES_REQUEST_INVALID_SEQUENCE_TYPE.get(
377                      StaticUtils.toHex(svElement.getType())));
378        }
379      }
380    }
381    catch (final LDAPException le)
382    {
383      throw le;
384    }
385    catch (final Exception e)
386    {
387      Debug.debugException(e);
388      throw new LDAPException(ResultCode.DECODING_ERROR,
389           ERR_STREAM_PROXY_VALUES_REQUEST_CANNOT_DECODE.get(
390                StaticUtils.getExceptionMessage(e)), e);
391    }
392
393    if (tmpBaseDN == null)
394    {
395      throw new LDAPException(ResultCode.DECODING_ERROR,
396           ERR_STREAM_PROXY_VALUES_REQUEST_NO_BASE_DN.get());
397    }
398
399    baseDN            = tmpBaseDN;
400    dnScope           = tmpScope;
401    returnRelativeDNs = tmpRelative;
402    backendSets       = Collections.unmodifiableList(tmpBackendSets);
403    attributes        = Collections.unmodifiableList(tmpAttrs);
404    valuesPerResponse = tmpNumValues;
405  }
406
407
408
409  /**
410   * Encodes the provided information into a form suitable for use as the value
411   * of this extended request.
412   *
413   * @param  baseDN             The base DN which indicates the portion of the
414   *                            DIT to target.
415   * @param  scope              The scope for which to return information about
416   *                            entry DNs in the specified portion of the DIT.
417   *                            This may be {@code null} if information about
418   *                            entry DNs should not be returned.
419   * @param  relativeDNs        Indicates whether DNs returned should be
420   *                            relative to the base DN rather than full DNs.
421   * @param  attributes         The names of the attributes for which to
422   *                            retrieve the values.  This may be {@code null}
423   *                            or empty if only entry DNs should be retrieved.
424   * @param  valuesPerResponse  The maximum number of values to include per
425   *                            response.  A value less than or equal to zero
426   *                            indicates that the server should choose an
427   *                            appropriate value.
428   * @param  backendSets        The list of backend sets defined in the
429   *                            Directory Proxy Server issuing the request.
430   *
431   * @return  The ASN.1 octet string containing the encoded value to use for
432   *          this extended request.
433   */
434  @NotNull()
435  private static ASN1OctetString encodeValue(@NotNull final String baseDN,
436               @Nullable final SearchScope scope,
437               final boolean relativeDNs,
438               @Nullable final List<String> attributes,
439               final int valuesPerResponse,
440               @NotNull final List<StreamProxyValuesBackendSet> backendSets)
441  {
442    Validator.ensureNotNull(baseDN, backendSets);
443    Validator.ensureFalse(backendSets.isEmpty());
444
445    final ArrayList<ASN1Element> svElements = new ArrayList<>(4);
446    svElements.add(new ASN1OctetString(TYPE_BASE_DN, baseDN));
447
448    if (scope != null)
449    {
450      final ArrayList<ASN1Element> idElements = new ArrayList<>(2);
451      idElements.add(new ASN1Enumerated(TYPE_SCOPE, scope.intValue()));
452
453      if (! relativeDNs)
454      {
455        idElements.add(new ASN1Boolean(TYPE_RELATIVE, relativeDNs));
456      }
457
458      svElements.add(new ASN1Sequence(TYPE_INCLUDE_DNS, idElements));
459    }
460
461    if ((attributes != null) && (! attributes.isEmpty()))
462    {
463      final ArrayList<ASN1Element> attrElements =
464           new ArrayList<>(attributes.size());
465      for (final String s : attributes)
466      {
467        attrElements.add(new ASN1OctetString(s));
468      }
469      svElements.add(new ASN1Sequence(TYPE_ATTRIBUTES, attrElements));
470    }
471
472    if (valuesPerResponse > 0)
473    {
474      svElements.add(new ASN1Integer(TYPE_VALUES_PER_RESPONSE,
475                                     valuesPerResponse));
476    }
477
478    final ASN1Element[] backendSetElements =
479         new ASN1Element[backendSets.size()];
480    for (int i=0; i < backendSetElements.length; i++)
481    {
482      backendSetElements[i] = backendSets.get(i).encode();
483    }
484    svElements.add(new ASN1Sequence(TYPE_BACKEND_SETS, backendSetElements));
485
486    return new ASN1OctetString(new ASN1Sequence(svElements).encode());
487  }
488
489
490
491  /**
492   * Retrieves the base DN for this request.
493   *
494   * @return  The base DN for this request.
495   */
496  @NotNull()
497  public String getBaseDN()
498  {
499    return baseDN;
500  }
501
502
503
504  /**
505   * Retrieves the scope for entry DNs to be included in intermediate responses.
506   *
507   * @return  The scope for entry DNs to be included in intermediate responses,
508   *          or {@code null} if information about entry DNs should not be
509   *          returned.
510   */
511  @Nullable()
512  public SearchScope getDNScope()
513  {
514    return dnScope;
515  }
516
517
518
519  /**
520   * Indicates whether entry DN values returned should be relative to the
521   * provided base DN.
522   *
523   * @return  {@code true} if entry DN values returned should be relative to the
524   *          provided base DN, or {@code false} if they should be complete DNs.
525   */
526  public boolean returnRelativeDNs()
527  {
528    return returnRelativeDNs;
529  }
530
531
532
533  /**
534   * Retrieves the list of names of attributes whose values should be returned
535   * to the client.
536   *
537   * @return  The list of names of attributes whose values should be returned to
538   *          the client, or an empty list if only information about entry DNs
539   *          should be returned.
540   */
541  @NotNull()
542  public List<String> getAttributes()
543  {
544    return attributes;
545  }
546
547
548
549  /**
550   * Retrieves the maximum number of values that should be included in each
551   * stream proxy values intermediate response.
552   *
553   * @return  The maximum number of values that should be included in each
554   *          stream proxy values intermediate response, or 0 if the server
555   *          should choose the appropriate number of values per response.
556   */
557  public int getValuesPerResponse()
558  {
559    return valuesPerResponse;
560  }
561
562
563
564  /**
565   * Retrieves the list of backend sets defined in the Directory Proxy Server
566   * instance issuing the request.
567   *
568   * @return  The list of backend sets defined in the Directory Proxy Server
569   *          instance issuing the request.
570   */
571  @NotNull()
572  public List<StreamProxyValuesBackendSet> getBackendSets()
573  {
574    return backendSets;
575  }
576
577
578
579  /**
580   * {@inheritDoc}
581   */
582  @Override()
583  @NotNull()
584  public StreamProxyValuesExtendedRequest duplicate()
585  {
586    return duplicate(getControls());
587  }
588
589
590
591  /**
592   * {@inheritDoc}
593   */
594  @Override()
595  @NotNull()
596  public StreamProxyValuesExtendedRequest duplicate(
597              @Nullable final Control[] controls)
598  {
599    final StreamProxyValuesExtendedRequest r =
600         new StreamProxyValuesExtendedRequest(baseDN, dnScope,
601              returnRelativeDNs, attributes, valuesPerResponse, backendSets,
602              controls);
603    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
604    r.setIntermediateResponseListener(getIntermediateResponseListener());
605    r.setReferralDepth(getReferralDepth());
606    r.setReferralConnector(getReferralConnectorInternal());
607    return r;
608  }
609
610
611
612  /**
613   * {@inheritDoc}
614   */
615  @Override()
616  @NotNull()
617  public String getExtendedRequestName()
618  {
619    return INFO_EXTENDED_REQUEST_NAME_STREAM_PROXY_VALUES.get();
620  }
621
622
623
624  /**
625   * {@inheritDoc}
626   */
627  @Override()
628  public void toString(@NotNull final StringBuilder buffer)
629  {
630    buffer.append("StreamProxyValuesExtendedRequest(baseDN='");
631    buffer.append(baseDN);
632    buffer.append('\'');
633
634    if (dnScope != null)
635    {
636      buffer.append(", scope='");
637      buffer.append(dnScope.getName());
638      buffer.append("', returnRelativeDNs=");
639      buffer.append(returnRelativeDNs);
640    }
641
642    buffer.append(", attributes={");
643    if (! attributes.isEmpty())
644    {
645      final Iterator<String> iterator = attributes.iterator();
646      while (iterator.hasNext())
647      {
648        buffer.append('\'');
649        buffer.append(iterator.next());
650        buffer.append('\'');
651
652        if (iterator.hasNext())
653        {
654          buffer.append(", ");
655        }
656      }
657    }
658    buffer.append('}');
659
660    if (valuesPerResponse > 0)
661    {
662      buffer.append(", valuesPerResponse=");
663      buffer.append(valuesPerResponse);
664    }
665
666    buffer.append(", backendSets={");
667    final Iterator<StreamProxyValuesBackendSet> setIterator =
668         backendSets.iterator();
669    while (setIterator.hasNext())
670    {
671      setIterator.next().toString(buffer);
672      if (setIterator.hasNext())
673      {
674        buffer.append(", ");
675      }
676    }
677    buffer.append('}');
678
679    final Control[] controls = getControls();
680    if (controls.length > 0)
681    {
682      buffer.append(", controls={");
683      for (int i=0; i < controls.length; i++)
684      {
685        if (i > 0)
686        {
687          buffer.append(", ");
688        }
689
690        buffer.append(controls[i]);
691      }
692      buffer.append('}');
693    }
694
695    buffer.append(')');
696  }
697}