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