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