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