001/*
002 * Copyright 2009-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2009-2022 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) 2009-2022 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.List;
042
043import com.unboundid.asn1.ASN1Boolean;
044import com.unboundid.asn1.ASN1Element;
045import com.unboundid.asn1.ASN1OctetString;
046import com.unboundid.asn1.ASN1Sequence;
047import com.unboundid.asn1.ASN1Set;
048import com.unboundid.ldap.sdk.LDAPException;
049import com.unboundid.ldap.sdk.ResultCode;
050import com.unboundid.util.Debug;
051import com.unboundid.util.NotMutable;
052import com.unboundid.util.NotNull;
053import com.unboundid.util.Nullable;
054import com.unboundid.util.StaticUtils;
055import com.unboundid.util.ThreadSafety;
056import com.unboundid.util.ThreadSafetyLevel;
057import com.unboundid.util.Validator;
058
059import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
060
061
062
063/**
064 * This class provides an implementation of a join rule as used by the LDAP join
065 * request control.  See the class-level documentation for the
066 * {@link JoinRequestControl} class for additional information and an example
067 * demonstrating its use.
068 * <BR>
069 * <BLOCKQUOTE>
070 *   <B>NOTE:</B>  This class, and other classes within the
071 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
072 *   supported for use against Ping Identity, UnboundID, and
073 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
074 *   for proprietary functionality or for external specifications that are not
075 *   considered stable or mature enough to be guaranteed to work in an
076 *   interoperable way with other types of LDAP servers.
077 * </BLOCKQUOTE>
078 * <BR>
079 * Join rules are encoded as follows:
080 * <PRE>
081 *   JoinRule ::= CHOICE {
082 *        and               [0] SET (1 .. MAX) of JoinRule,
083 *        or                [1] SET (1 .. MAX) of JoinRule,
084 *        dnJoin            [2] AttributeDescription,
085 *        equalityJoin      [3] JoinRuleAssertion,
086 *        containsJoin      [4] JoinRuleAssertion,
087 *        reverseDNJoin     [5] AttributeDescription,
088 *        ... }
089 *
090 *   JoinRuleAssertion ::= SEQUENCE {
091 *        sourceAttribute     AttributeDescription,
092 *        targetAttribute     AttributeDescription,
093 *        matchAll            BOOLEAN DEFAULT FALSE }
094 * </PRE>
095 */
096@NotMutable()
097@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
098public final class JoinRule
099       implements Serializable
100{
101  /**
102   * The join rule type that will be used for AND join rules.
103   */
104  public static final byte JOIN_TYPE_AND = (byte) 0xA0;
105
106
107
108  /**
109   * The join rule type that will be used for OR join rules.
110   */
111  public static final byte JOIN_TYPE_OR = (byte) 0xA1;
112
113
114
115  /**
116   * The join rule type that will be used for DN join rules.
117   */
118  public static final byte JOIN_TYPE_DN = (byte) 0x82;
119
120
121
122  /**
123   * The join rule type that will be used for equality join rules.
124   */
125  public static final byte JOIN_TYPE_EQUALITY = (byte) 0xA3;
126
127
128
129  /**
130   * The join rule type that will be used for contains join rules.
131   */
132  public static final byte JOIN_TYPE_CONTAINS = (byte) 0xA4;
133
134
135
136  /**
137   * The join rule type that will be used for reverse DN join rules.
138   */
139  public static final byte JOIN_TYPE_REVERSE_DN = (byte) 0x85;
140
141
142
143  /**
144   * An empty array of join rules that will be used as the set of components
145   * for DN and equality join rules.
146   */
147  @NotNull private static final JoinRule[] NO_RULES = new JoinRule[0];
148
149
150
151  /**
152   * The serial version UID for this serializable class.
153   */
154  private static final long serialVersionUID = 9041070342511946580L;
155
156
157
158  // Indicates whether all values of a multivalued source attribute must be
159  // present in the target entry for it to be considered a match.
160  private final boolean matchAll;
161
162  // The BER type for this join rule.
163  private final byte type;
164
165  // The set of subordinate components for this join rule.
166  @NotNull private final JoinRule[] components;
167
168  // The name of the source attribute for this join rule.
169  @Nullable private final String sourceAttribute;
170
171  // The name of the target attribute for this join rule.
172  @Nullable private final String targetAttribute;
173
174
175
176  /**
177   * Creates a new join rule with the provided information.
178   *
179   * @param  type             The BER type for this join rule.
180   * @param  components       The set of subordinate components for this join
181   *                          rule.
182   * @param  sourceAttribute  The name of the source attribute for this join
183   *                          rule.
184   * @param  targetAttribute  The name of the target attribute for this join
185   *                          rule.
186   * @param  matchAll         Indicates whether all values of a multivalued
187   *                          source attribute must be present in the target
188   *                          entry for it to be considered a match.
189   */
190  private JoinRule(final byte type, @NotNull final JoinRule[] components,
191                   @Nullable final String sourceAttribute,
192                   @Nullable final String targetAttribute,
193                   final boolean matchAll)
194  {
195    this.type            = type;
196    this.components      = components;
197    this.sourceAttribute = sourceAttribute;
198    this.targetAttribute = targetAttribute;
199    this.matchAll        = matchAll;
200  }
201
202
203
204  /**
205   * Creates an AND join rule in which all of the contained join rules must
206   * match an entry for it to be included in the join.
207   *
208   * @param  components  The set of components to include in this join.  It must
209   *                     not be {@code null} or empty.
210   *
211   * @return  The created AND join rule.
212   */
213  @NotNull()
214  public static JoinRule createANDRule(@NotNull final JoinRule... components)
215  {
216    Validator.ensureNotNull(components);
217    Validator.ensureFalse(components.length == 0);
218
219    return new JoinRule(JOIN_TYPE_AND, components, null, null, false);
220  }
221
222
223
224  /**
225   * Creates an AND join rule in which all of the contained join rules must
226   * match an entry for it to be included in the join.
227   *
228   * @param  components  The set of components to include in this join.  It must
229   *                     not be {@code null} or empty.
230   *
231   * @return  The created AND join rule.
232   */
233  @NotNull()
234  public static JoinRule createANDRule(@NotNull final List<JoinRule> components)
235  {
236    Validator.ensureNotNull(components);
237    Validator.ensureFalse(components.isEmpty());
238
239    final JoinRule[] compArray = new JoinRule[components.size()];
240    return new JoinRule(JOIN_TYPE_AND, components.toArray(compArray), null,
241                        null, false);
242  }
243
244
245
246  /**
247   * Creates an OR join rule in which at least one of the contained join rules
248   * must match an entry for it to be included in the join.
249   *
250   * @param  components  The set of components to include in this join.  It must
251   *                     not be {@code null} or empty.
252   *
253   * @return  The created OR join rule.
254   */
255  @NotNull()
256  public static JoinRule createORRule(@NotNull final JoinRule... components)
257  {
258    Validator.ensureNotNull(components);
259    Validator.ensureFalse(components.length == 0);
260
261    return new JoinRule(JOIN_TYPE_OR, components, null, null, false);
262  }
263
264
265
266  /**
267   * Creates an OR join rule in which at least one of the contained join rules
268   * must match an entry for it to be included in the join.
269   *
270   * @param  components  The set of components to include in this join.  It must
271   *                     not be {@code null} or empty.
272   *
273   * @return  The created OR join rule.
274   */
275  @NotNull()
276  public static JoinRule createORRule(@NotNull final List<JoinRule> components)
277  {
278    Validator.ensureNotNull(components);
279    Validator.ensureFalse(components.isEmpty());
280
281    final JoinRule[] compArray = new JoinRule[components.size()];
282    return new JoinRule(JOIN_TYPE_OR, components.toArray(compArray), null,
283                        null, false);
284  }
285
286
287
288  /**
289   * Creates a DN join rule in which the value(s) of the source attribute must
290   * specify the DN(s) of the target entries to include in the join.
291   *
292   * @param  sourceAttribute  The name or OID of the attribute in the source
293   *                          entry whose values contain the DNs of the entries
294   *                          to be included in the join.  It must not be
295   *                          {@code null}, and it must be associated with a
296   *                          distinguished name or name and optional UID
297   *                          syntax.
298   *
299   * @return  The created DN join rule.
300   */
301  @NotNull()
302  public static JoinRule createDNJoin(@NotNull final String sourceAttribute)
303  {
304    Validator.ensureNotNull(sourceAttribute);
305
306    return new JoinRule(JOIN_TYPE_DN, NO_RULES, sourceAttribute, null, false);
307  }
308
309
310
311  /**
312   * Creates an equality join rule in which the value(s) of the source attribute
313   * in the source entry must be equal to the value(s) of the target attribute
314   * of a target entry for it to be included in the join.
315   *
316   * @param  sourceAttribute  The name or OID of the attribute in the source
317   *                          entry whose value(s) should be matched in target
318   *                          entries to be included in the join.  It must not
319   *                          be {@code null}.
320   * @param  targetAttribute  The name or OID of the attribute whose value(s)
321   *                          must match the source value(s) in entries included
322   *                          in the join.  It must not be {@code null}.
323   * @param  matchAll         Indicates whether all values of a multivalued
324   *                          source attribute must be present in the target
325   *                          entry for it to be considered a match.
326   *
327   * @return  The created equality join rule.
328   */
329  @NotNull()
330  public static JoinRule createEqualityJoin(
331              @NotNull final String sourceAttribute,
332              @NotNull final String targetAttribute,
333              final boolean matchAll)
334  {
335    Validator.ensureNotNull(sourceAttribute, targetAttribute);
336
337    return new JoinRule(JOIN_TYPE_EQUALITY, NO_RULES, sourceAttribute,
338                        targetAttribute, matchAll);
339  }
340
341
342
343  /**
344   * Creates an equality join rule in which the value(s) of the source attribute
345   * in the source entry must be equal to or a substring of the value(s) of the
346   * target attribute of a target entry for it to be included in the join.
347   *
348   * @param  sourceAttribute  The name or OID of the attribute in the source
349   *                          entry whose value(s) should be matched in target
350   *                          entries to be included in the join.  It must not
351   *                          be {@code null}.
352   * @param  targetAttribute  The name or OID of the attribute whose value(s)
353   *                          must equal or contain the source value(s) in
354   *                          entries included in the join.  It must not be
355   *                          {@code null}.
356   * @param  matchAll         Indicates whether all values of a multivalued
357   *                          source attribute must be present in the target
358   *                          entry for it to be considered a match.
359   *
360   * @return  The created equality join rule.
361   */
362  @NotNull()
363  public static JoinRule createContainsJoin(
364              @NotNull final String sourceAttribute,
365              @NotNull final String targetAttribute,
366              final boolean matchAll)
367  {
368    Validator.ensureNotNull(sourceAttribute, targetAttribute);
369
370    return new JoinRule(JOIN_TYPE_CONTAINS, NO_RULES, sourceAttribute,
371                        targetAttribute, matchAll);
372  }
373
374
375
376  /**
377   * Creates a reverse DN join rule in which the target entries to include in
378   * the join must include a specified attribute that contains the DN of the
379   * source entry.
380   *
381   * @param  targetAttribute  The name or OID of the attribute in the target
382   *                          entries which must contain the DN of the source
383   *                          entry.  It must not be {@code null}, and it must
384   *                          be associated with a distinguished nme or name and
385   *                          optional UID syntax.
386   *
387   * @return  The created reverse DN join rule.
388   */
389  @NotNull()
390  public static JoinRule createReverseDNJoin(
391              @NotNull final String targetAttribute)
392  {
393    Validator.ensureNotNull(targetAttribute);
394
395    return new JoinRule(JOIN_TYPE_REVERSE_DN, NO_RULES, null, targetAttribute,
396         false);
397  }
398
399
400
401  /**
402   * Retrieves the join rule type for this join rule.
403   *
404   * @return  The join rule type for this join rule.
405   */
406  public byte getType()
407  {
408    return type;
409  }
410
411
412
413  /**
414   * Retrieves the set of subordinate components for this AND or OR join rule.
415   *
416   * @return  The set of subordinate components for this AND or OR join rule, or
417   *          an empty list if this is not an AND or OR join rule.
418   */
419  @NotNull()
420  public JoinRule[] getComponents()
421  {
422    return components;
423  }
424
425
426
427  /**
428   * Retrieves the name of the source attribute for this DN, equality, or
429   * contains join rule.
430   *
431   * @return  The name of the source attribute for this DN, equality, or
432   *          contains join rule, or {@code null} if this is some other type of
433   *          join rule.
434   */
435  @Nullable()
436  public String getSourceAttribute()
437  {
438    return sourceAttribute;
439  }
440
441
442
443  /**
444   * Retrieves the name of the target attribute for this reverse DN, equality,
445   * or contains join rule.
446   *
447   * @return  The name of the target attribute for this reverse DN, equality, or
448   *          contains join rule, or {@code null} if this is some other type of
449   *          join rule.
450   */
451  @Nullable()
452  public String getTargetAttribute()
453  {
454    return targetAttribute;
455  }
456
457
458
459  /**
460   * Indicates whether all values of a multivalued source attribute must be
461   * present in a target entry for it to be considered a match.  The return
462   * value will only be meaningful for equality join rules.
463   *
464   * @return  {@code true} if all values of the source attribute must be
465   *          included in the target attribute of an entry for it to be
466   *          considered for inclusion in the join, or {@code false} if it is
467   *          only necessary for at least one of the values to be included in a
468   *          target entry for it to be considered for inclusion in the join.
469   */
470  public boolean matchAll()
471  {
472    return matchAll;
473  }
474
475
476
477  /**
478   * Encodes this join rule as appropriate for inclusion in an LDAP join
479   * request control.
480   *
481   * @return  The encoded representation of this join rule.
482   */
483  @NotNull()
484  ASN1Element encode()
485  {
486    switch (type)
487    {
488      case JOIN_TYPE_AND:
489      case JOIN_TYPE_OR:
490        final ASN1Element[] compElements = new ASN1Element[components.length];
491        for (int i=0; i < components.length; i++)
492        {
493          compElements[i] = components[i].encode();
494        }
495        return new ASN1Set(type, compElements);
496
497      case JOIN_TYPE_DN:
498        return new ASN1OctetString(type, sourceAttribute);
499
500      case JOIN_TYPE_EQUALITY:
501      case JOIN_TYPE_CONTAINS:
502        if (matchAll)
503        {
504          return new ASN1Sequence(type,
505               new ASN1OctetString(sourceAttribute),
506               new ASN1OctetString(targetAttribute),
507               new ASN1Boolean(matchAll));
508        }
509        else
510        {
511          return new ASN1Sequence(type,
512               new ASN1OctetString(sourceAttribute),
513               new ASN1OctetString(targetAttribute));
514        }
515    case JOIN_TYPE_REVERSE_DN:
516      return new ASN1OctetString(type, targetAttribute);
517
518      default:
519        // This should never happen.
520        return null;
521    }
522  }
523
524
525
526  /**
527   * Decodes the provided ASN.1 element as a join rule.
528   *
529   * @param  element  The element to be decoded.
530   *
531   * @return  The decoded join rule.
532   *
533   * @throws  LDAPException  If a problem occurs while attempting to decode the
534   *                         provided element as a join rule.
535   */
536  @NotNull()
537  static JoinRule decode(@NotNull final ASN1Element element)
538         throws LDAPException
539  {
540    final byte elementType = element.getType();
541    switch (elementType)
542    {
543      case JOIN_TYPE_AND:
544      case JOIN_TYPE_OR:
545        try
546        {
547          final ASN1Element[] elements =
548               ASN1Set.decodeAsSet(element).elements();
549          final JoinRule[] rules = new JoinRule[elements.length];
550          for (int i=0; i < rules.length; i++)
551          {
552            rules[i] = decode(elements[i]);
553          }
554
555          return new JoinRule(elementType, rules, null, null, false);
556        }
557        catch (final Exception e)
558        {
559          Debug.debugException(e);
560
561          throw new LDAPException(ResultCode.DECODING_ERROR,
562               ERR_JOIN_RULE_CANNOT_DECODE.get(
563                    StaticUtils.getExceptionMessage(e)),
564               e);
565        }
566
567
568      case JOIN_TYPE_DN:
569        return new JoinRule(elementType, NO_RULES,
570             ASN1OctetString.decodeAsOctetString(element).stringValue(), null,
571             false);
572
573
574      case JOIN_TYPE_EQUALITY:
575      case JOIN_TYPE_CONTAINS:
576        try
577        {
578          final ASN1Element[] elements =
579               ASN1Sequence.decodeAsSequence(element).elements();
580
581          final String sourceAttribute =
582               elements[0].decodeAsOctetString().stringValue();
583          final String targetAttribute =
584               elements[1].decodeAsOctetString().stringValue();
585
586          boolean matchAll = false;
587          if (elements.length == 3)
588          {
589            matchAll = elements[2].decodeAsBoolean().booleanValue();
590          }
591
592          return new JoinRule(elementType, NO_RULES, sourceAttribute,
593               targetAttribute, matchAll);
594        }
595        catch (final Exception e)
596        {
597          Debug.debugException(e);
598
599          throw new LDAPException(ResultCode.DECODING_ERROR,
600               ERR_JOIN_RULE_CANNOT_DECODE.get(
601                    StaticUtils.getExceptionMessage(e)),
602               e);
603        }
604
605
606    case JOIN_TYPE_REVERSE_DN:
607      return new JoinRule(elementType, NO_RULES, null,
608           ASN1OctetString.decodeAsOctetString(element).stringValue(), false);
609
610
611      default:
612        throw new LDAPException(ResultCode.DECODING_ERROR,
613             ERR_JOIN_RULE_DECODE_INVALID_TYPE.get(
614                  StaticUtils.toHex(elementType)));
615    }
616  }
617
618
619
620  /**
621   * Retrieves a string representation of this join rule.
622   *
623   * @return  A string representation of this join rule.
624   */
625  @Override()
626  @NotNull()
627  public String toString()
628  {
629    final StringBuilder buffer = new StringBuilder();
630    toString(buffer);
631    return buffer.toString();
632  }
633
634
635
636  /**
637   * Appends a string representation of this join rule to the provided buffer.
638   *
639   * @param  buffer  The buffer to which the information should be appended.
640   */
641  public void toString(@NotNull final StringBuilder buffer)
642  {
643    switch (type)
644    {
645      case JOIN_TYPE_AND:
646        buffer.append("ANDJoinRule(components={");
647        for (int i=0; i < components.length; i++)
648        {
649          if (i > 0)
650          {
651            buffer.append(", ");
652          }
653          components[i].toString(buffer);
654        }
655        buffer.append("})");
656        break;
657
658      case JOIN_TYPE_OR:
659        buffer.append("ORJoinRule(components={");
660        for (int i=0; i < components.length; i++)
661        {
662          if (i > 0)
663          {
664            buffer.append(", ");
665          }
666          components[i].toString(buffer);
667        }
668        buffer.append("})");
669        break;
670
671      case JOIN_TYPE_DN:
672        buffer.append("DNJoinRule(sourceAttr=");
673        buffer.append(sourceAttribute);
674        buffer.append(')');
675        break;
676
677      case JOIN_TYPE_EQUALITY:
678        buffer.append("EqualityJoinRule(sourceAttr=");
679        buffer.append(sourceAttribute);
680        buffer.append(", targetAttr=");
681        buffer.append(targetAttribute);
682        buffer.append(", matchAll=");
683        buffer.append(matchAll);
684        buffer.append(')');
685        break;
686
687      case JOIN_TYPE_CONTAINS:
688        buffer.append("ContainsJoinRule(sourceAttr=");
689        buffer.append(sourceAttribute);
690        buffer.append(", targetAttr=");
691        buffer.append(targetAttribute);
692        buffer.append(", matchAll=");
693        buffer.append(matchAll);
694        buffer.append(')');
695        break;
696
697    case JOIN_TYPE_REVERSE_DN:
698      buffer.append("ReverseDNJoinRule(targetAttr=");
699      buffer.append(targetAttribute);
700      buffer.append(')');
701      break;
702    }
703  }
704}