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.controls;
022    
023    
024    
025    import java.io.Serializable;
026    import java.util.ArrayList;
027    
028    import com.unboundid.asn1.ASN1Boolean;
029    import com.unboundid.asn1.ASN1Element;
030    import com.unboundid.asn1.ASN1Enumerated;
031    import com.unboundid.asn1.ASN1Integer;
032    import com.unboundid.asn1.ASN1OctetString;
033    import com.unboundid.asn1.ASN1Sequence;
034    import com.unboundid.ldap.sdk.DereferencePolicy;
035    import com.unboundid.ldap.sdk.Filter;
036    import com.unboundid.ldap.sdk.LDAPException;
037    import com.unboundid.ldap.sdk.ResultCode;
038    import com.unboundid.ldap.sdk.SearchScope;
039    import com.unboundid.util.NotMutable;
040    import com.unboundid.util.ThreadSafety;
041    import com.unboundid.util.ThreadSafetyLevel;
042    
043    import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
044    import static com.unboundid.util.Debug.*;
045    import static com.unboundid.util.StaticUtils.*;
046    import static com.unboundid.util.Validator.*;
047    
048    
049    
050    /**
051     * <BLOCKQUOTE>
052     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
053     *   LDAP SDK for Java.  It is not available for use in applications that
054     *   include only the Standard Edition of the LDAP SDK, and is not supported for
055     *   use in conjunction with non-UnboundID products.
056     * </BLOCKQUOTE>
057     * This class contains a data structure which provides information about the
058     * value of an LDAP join request control, which may or may not include a nested
059     * join.  See the class-level documentation for the {@link JoinRequestControl}
060     * class for additional information and an example demonstrating its use.
061     * <BR><BR>
062     * The value of the join request control is encoded as follows:
063     * <PRE>
064     *   LDAPJoin ::= SEQUENCE {
065     *        joinRule         JoinRule,
066     *        baseObject       CHOICE {
067     *             useSearchBaseDN      [0] NULL,
068     *             useSourceEntryDN     [1] NULL,
069     *             useCustomBaseDN      [2] LDAPDN,
070     *             ... },
071     *        scope            [0] ENUMERATED {
072     *             baseObject             (0),
073     *             singleLevel            (1),
074     *             wholeSubtree           (2),
075     *             subordinateSubtree     (3),
076     *             ... } OPTIONAL,
077     *        derefAliases     [1] ENUMERATED {
078     *             neverDerefAliases       (0),
079     *             derefInSearching        (1),
080     *             derefFindingBaseObj     (2),
081     *             derefAlways             (3),
082     *             ... } OPTIONAL,
083     *        sizeLimit        [2] INTEGER (0 .. maxInt) OPTIONAL,
084     *        filter           [3] Filter OPTIONAL,
085     *        attributes       [4] AttributeSelection OPTIONAL,
086     *        requireMatch     [5] BOOLEAN DEFAULT FALSE,
087     *        nestedJoin       [6] LDAPJoin OPTIONAL,
088     *        ... }
089     * </PRE>
090     */
091    @NotMutable()
092    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
093    public final class JoinRequestValue
094           implements Serializable
095    {
096      /**
097       * The set of attributes that will be used if all user attributes should be
098       * requested.
099       */
100      private static final String[] NO_ATTRIBUTES = NO_STRINGS;
101    
102    
103    
104      /**
105       * The BER type to use for the scope element.
106       */
107      private static final byte TYPE_SCOPE = (byte) 0x80;
108    
109    
110    
111      /**
112       * The BER type to use for the dereference policy element.
113       */
114      private static final byte TYPE_DEREF_POLICY = (byte) 0x81;
115    
116    
117    
118      /**
119       * The BER type to use for the size limit element.
120       */
121      private static final byte TYPE_SIZE_LIMIT = (byte) 0x82;
122    
123    
124    
125      /**
126       * The BER type to use for the filter element.
127       */
128      private static final byte TYPE_FILTER = (byte) 0xA3;
129    
130    
131    
132      /**
133       * The BER type to use for the attributes element.
134       */
135      private static final byte TYPE_ATTRIBUTES = (byte) 0xA4;
136    
137    
138    
139      /**
140       * The BER type to use for the require match element.
141       */
142      private static final byte TYPE_REQUIRE_MATCH = (byte) 0x85;
143    
144    
145    
146      /**
147       * The BER type to use for the nested join element.
148       */
149      private static final byte TYPE_NESTED_JOIN = (byte) 0xA6;
150    
151    
152    
153      /**
154       * The serial version UID for this serializable class.
155       */
156      private static final long serialVersionUID = 4675881185117657177L;
157    
158    
159    
160      // Indicates whether to require at least one entry to match the join
161      // criteria for the entry to be returned.
162      private final boolean requireMatch;
163    
164      // The dereference policy for this join request value.
165      private final DereferencePolicy derefPolicy;
166    
167      // The filter for this join request value.
168      private final Filter filter;
169    
170      // The size limit for this join request value.
171      private final Integer sizeLimit;
172    
173      // The base DN to use for this join request value.
174      private final JoinBaseDN baseDN;
175    
176      // The nested join criteria for this join request value.
177      private final JoinRequestValue nestedJoin;
178    
179      // The join rule for this join request value.
180      private final JoinRule joinRule;
181    
182      // The scope for this join request value.
183      private final SearchScope scope;
184    
185      // The set of attributes to include in entries matching the join criteria.
186      private final String[] attributes;
187    
188    
189    
190      /**
191       * Creates a new join request value with the provided information.
192       *
193       * @param  joinRule      The join rule for this join request value.  It must
194       *                       not be {@code null}.
195       * @param  baseDN        The base DN for this join request value.  It must
196       *                       not be {@code null}.
197       * @param  scope         The scope for this join request value.  It may be
198       *                       {@code null} if the scope from the associated search
199       *                       request should be used.
200       * @param  derefPolicy   The alias dereferencing policy for this join request
201       *                       value.  It may be {@code null} if the dereference
202       *                       policy from the associated search request should be
203       *                       used.
204       * @param  sizeLimit     The maximum number of entries to allow when
205       *                       performing the join.  It may be {@code null} if the
206       *                       size limit from the associated search request should
207       *                       be used.
208       * @param  filter        An additional filter which must match target entries
209       *                       for them to be included in the join.  This may be
210       *                       {@code null} if no additional filter is required and
211       *                       the join rule should be the only criteria used when
212       *                       performing the join.
213       * @param  attributes    The set of attributes that the client wishes to be
214       *                       included in joined entries.  It may be {@code null}
215       *                       or empty to indicate that all user attributes should
216       *                       be included.  It may also contain special values like
217       *                       "1.1" to indicate that no attributes should be
218       *                       included, "*" to indicate that all user attributes
219       *                       should be included, "+" to indicate that all
220       *                       operational attributes should be included, or
221       *                       "@ocname" to indicate that all required and optional
222       *                       attributes associated with the "ocname" object class
223       *                       should be included.
224       * @param  requireMatch  Indicates whether a search result entry is required
225       *                       to be joined with at least one entry for it to be
226       *                       returned to the client.
227       * @param  nestedJoin    A set of join criteria that should be applied to
228       *                       entries joined with this join request value.  It may
229       *                       be {@code null} if no nested join is needed.
230       */
231      public JoinRequestValue(final JoinRule joinRule, final JoinBaseDN baseDN,
232                  final SearchScope scope, final DereferencePolicy derefPolicy,
233                  final Integer sizeLimit, final Filter filter,
234                  final String[] attributes, final boolean requireMatch,
235                  final JoinRequestValue nestedJoin)
236      {
237        ensureNotNull(joinRule, baseDN);
238    
239        this.joinRule     = joinRule;
240        this.baseDN       = baseDN;
241        this.scope        = scope;
242        this.derefPolicy  = derefPolicy;
243        this.sizeLimit    = sizeLimit;
244        this.filter       = filter;
245        this.requireMatch = requireMatch;
246        this.nestedJoin   = nestedJoin;
247    
248        if (attributes == null)
249        {
250          this.attributes = NO_ATTRIBUTES;
251        }
252        else
253        {
254          this.attributes = attributes;
255        }
256      }
257    
258    
259    
260      /**
261       * Retrieves the join rule for this join request value.
262       *
263       * @return  The join rule for this join request value.
264       */
265      public JoinRule getJoinRule()
266      {
267        return joinRule;
268      }
269    
270    
271    
272      /**
273       * Retrieves the join base DN for this join request value.
274       *
275       * @return  The join base DN for this join request value.
276       */
277      public JoinBaseDN getBaseDN()
278      {
279        return baseDN;
280      }
281    
282    
283    
284      /**
285       * Retrieves the scope for this join request value.
286       *
287       * @return  The scope for this join request value, or {@code null} if the
288       *          scope from the associated search request should be used.
289       */
290      public SearchScope getScope()
291      {
292        return scope;
293      }
294    
295    
296    
297      /**
298       * Retrieves the alias dereferencing policy for this join request value.
299       *
300       * @return  The alias dereferencing policy for this join request value, or
301       *          {@code null} if the policy from the associated search request
302       *          should be used.
303       */
304      public DereferencePolicy getDerefPolicy()
305      {
306        return derefPolicy;
307      }
308    
309    
310    
311      /**
312       * Retrieves the size limit for this join request value.
313       *
314       * @return  The size limit for this join request value, or {@code null} if the
315       *          size limit from the associated search request should be used.
316       */
317      public Integer getSizeLimit()
318      {
319        return sizeLimit;
320      }
321    
322    
323    
324      /**
325       * Retrieves a filter with additional criteria that must match a target entry
326       * for it to be joined with a search result entry.
327       *
328       * @return  A filter with additional criteria that must match a target entry
329       *          for it to be joined with a search result entry, or {@code null} if
330       *          no additional filter is needed.
331       */
332      public Filter getFilter()
333      {
334        return filter;
335      }
336    
337    
338    
339      /**
340       * Retrieves the set of requested attributes that should be included in
341       * joined entries.
342       *
343       * @return  The set of requested attributes that should be included in joined
344       *          entries, or an empty array if all user attributes should be
345       *          requested.
346       */
347      public String[] getAttributes()
348      {
349        return attributes;
350      }
351    
352    
353    
354      /**
355       * Indicates whether a search result entry will be required to be joined with
356       * at least one entry for that entry to be returned to the client.
357       *
358       * @return  {@code true} if a search result entry must be joined with at least
359       *          one other entry for it to be returned to the client, or
360       *          {@code false} if a search result entry may be returned even if it
361       *          is not joined with any other entries.
362       */
363      public boolean requireMatch()
364      {
365        return requireMatch;
366      }
367    
368    
369    
370      /**
371       * Retrieves the nested join for this join request value, if defined.
372       *
373       * @return  The nested join for this join request value, or {@code null} if
374       *          there is no nested join for this join request value.
375       */
376      public JoinRequestValue getNestedJoin()
377      {
378        return nestedJoin;
379      }
380    
381    
382    
383      /**
384       * Encodes this join request value as appropriate for inclusion in the join
385       * request control.
386       *
387       * @return  The ASN.1 element containing the encoded join request value.
388       */
389      ASN1Element encode()
390      {
391        final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(9);
392    
393        elements.add(joinRule.encode());
394        elements.add(baseDN.encode());
395    
396        if (scope != null)
397        {
398          elements.add(new ASN1Enumerated(TYPE_SCOPE, scope.intValue()));
399        }
400    
401        if (derefPolicy != null)
402        {
403          elements.add(new ASN1Enumerated(TYPE_DEREF_POLICY,
404               derefPolicy.intValue()));
405        }
406    
407        if (sizeLimit != null)
408        {
409          elements.add(new ASN1Integer(TYPE_SIZE_LIMIT, sizeLimit));
410        }
411    
412        if (filter != null)
413        {
414          elements.add(new ASN1OctetString(TYPE_FILTER, filter.encode().encode()));
415        }
416    
417        if ((attributes != null) && (attributes.length > 0))
418        {
419          final ASN1Element[] attrElements = new ASN1Element[attributes.length];
420          for (int i=0; i < attributes.length; i++)
421          {
422            attrElements[i] = new ASN1OctetString(attributes[i]);
423          }
424          elements.add(new ASN1Sequence(TYPE_ATTRIBUTES, attrElements));
425        }
426    
427        if (requireMatch)
428        {
429          elements.add(new ASN1Boolean(TYPE_REQUIRE_MATCH, requireMatch));
430        }
431    
432        if (nestedJoin != null)
433        {
434          elements.add(new ASN1OctetString(TYPE_NESTED_JOIN,
435               nestedJoin.encode().getValue()));
436        }
437    
438        return new ASN1Sequence(elements);
439      }
440    
441    
442    
443      /**
444       * Decodes the provided ASN.1 element as a join request value.
445       *
446       * @param  element  The element to be decoded.
447       *
448       * @return  The decoded join request value.
449       *
450       * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
451       *                         a join request value.
452       */
453      static JoinRequestValue decode(final ASN1Element element)
454             throws LDAPException
455      {
456        try
457        {
458          final ASN1Element[] elements =
459               ASN1Sequence.decodeAsSequence(element).elements();
460          final JoinRule   joinRule = JoinRule.decode(elements[0]);
461          final JoinBaseDN baseDN   = JoinBaseDN.decode(elements[1]);
462    
463          SearchScope       scope        = null;
464          DereferencePolicy derefPolicy  = null;
465          Integer           sizeLimit    = null;
466          Filter            filter       = null;
467          String[]          attributes   = NO_ATTRIBUTES;
468          boolean           requireMatch = false;
469          JoinRequestValue  nestedJoin   = null;
470    
471          for (int i=2; i < elements.length; i++)
472          {
473            switch (elements[i].getType())
474            {
475              case TYPE_SCOPE:
476                scope = SearchScope.valueOf(
477                     ASN1Enumerated.decodeAsEnumerated(elements[i]).intValue());
478                break;
479    
480              case TYPE_DEREF_POLICY:
481                derefPolicy = DereferencePolicy.valueOf(
482                     ASN1Enumerated.decodeAsEnumerated(elements[i]).intValue());
483                break;
484    
485              case TYPE_SIZE_LIMIT:
486                sizeLimit = ASN1Integer.decodeAsInteger(elements[i]).intValue();
487                break;
488    
489              case TYPE_FILTER:
490                filter = Filter.decode(ASN1Element.decode(elements[i].getValue()));
491                break;
492    
493              case TYPE_ATTRIBUTES:
494                final ArrayList<String> attrList = new ArrayList<String>();
495                for (final ASN1Element e :
496                     ASN1Sequence.decodeAsSequence(elements[i]).elements())
497                {
498                  attrList.add(
499                       ASN1OctetString.decodeAsOctetString(e).stringValue());
500                }
501    
502                attributes = new String[attrList.size()];
503                attrList.toArray(attributes);
504                break;
505    
506              case TYPE_REQUIRE_MATCH:
507                requireMatch =
508                     ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
509                break;
510    
511              case TYPE_NESTED_JOIN:
512                nestedJoin = decode(elements[i]);
513                break;
514    
515              default:
516                throw new LDAPException(ResultCode.DECODING_ERROR,
517                     ERR_JOIN_REQUEST_VALUE_INVALID_ELEMENT_TYPE.get(
518                          elements[i].getType()));
519            }
520          }
521    
522          return new JoinRequestValue(joinRule, baseDN, scope, derefPolicy,
523               sizeLimit, filter, attributes, requireMatch, nestedJoin);
524        }
525        catch (Exception e)
526        {
527          debugException(e);
528    
529          throw new LDAPException(ResultCode.DECODING_ERROR,
530               ERR_JOIN_REQUEST_VALUE_CANNOT_DECODE.get(getExceptionMessage(e)), e);
531        }
532      }
533    
534    
535    
536      /**
537       * Retrieves a string representation of this join request value.
538       *
539       * @return  A string representation of this join request value.
540       */
541      @Override()
542      public String toString()
543      {
544        final StringBuilder buffer = new StringBuilder();
545        toString(buffer);
546        return buffer.toString();
547      }
548    
549    
550    
551      /**
552       * Appends a string representation of this join request value to the provided
553       * buffer.
554       *
555       * @param  buffer  The buffer to which the information should be appended.
556       */
557      public void toString(final StringBuilder buffer)
558      {
559        buffer.append("JoinRequestValue(joinRule=");
560        joinRule.toString(buffer);
561        buffer.append(", baseDN=");
562        baseDN.toString(buffer);
563        buffer.append(", scope=");
564        buffer.append(String.valueOf(scope));
565        buffer.append(", derefPolicy=");
566        buffer.append(String.valueOf(derefPolicy));
567        buffer.append(", sizeLimit=");
568        buffer.append(sizeLimit);
569        buffer.append(", filter=");
570    
571        if (filter == null)
572        {
573          buffer.append("null");
574        }
575        else
576        {
577          buffer.append('\'');
578          filter.toString(buffer);
579          buffer.append('\'');
580        }
581    
582        buffer.append(", attributes={");
583    
584        for (int i=0; i < attributes.length; i++)
585        {
586          if (i > 0)
587          {
588            buffer.append(", ");
589          }
590          buffer.append(attributes[i]);
591        }
592    
593        buffer.append("}, requireMatch=");
594        buffer.append(requireMatch);
595        buffer.append(", nestedJoin=");
596    
597        if (nestedJoin == null)
598        {
599          buffer.append("null");
600        }
601        else
602        {
603          nestedJoin.toString(buffer);
604        }
605    
606        buffer.append(')');
607      }
608    }