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 }