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