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