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