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