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