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